Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #161 from jenkinsci/StepExecutionIterator-JENKINS-…
Browse files Browse the repository at this point in the history
…26163

[JENKINS-26163] Fixed handling of AbstractStepExecutionImpl.onResume
  • Loading branch information
jglick committed Jul 30, 2015
2 parents fe93296 + 0951789 commit fb3e89a
Show file tree
Hide file tree
Showing 14 changed files with 366 additions and 92 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -4,6 +4,7 @@ Only noting significant user-visible or major API changes, not internal code cle

## 1.9 (upcoming)

* [JENKINS-26163](https://issues.jenkins-ci.org/browse/JENKINS-26163): `AbstractStepExecutionImpl.onResume` was not (usually) being called for block-scoped steps, leading to incorrect behavior after Jenkins restart for flows inside `timeout` or `waitUntil`.
* [JENKINS-26761](https://issues.jenkins-ci.org/browse/JENKINS-26761): `NullPointerException` from Git commit notification requests under unknown circumstances; improved robustness and logging.

## 1.9-beta-1 (Jul 27 2015)
Expand Down
Expand Up @@ -24,39 +24,56 @@

package org.jenkinsci.plugins.workflow;

import com.google.common.util.concurrent.ListenableFuture;
import hudson.AbortException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.CpsStepContext;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.MemoryAssert;
import org.jvnet.hudson.test.RestartableJenkinsRule;

public class CpsFlowExecutionTest {

@Rule public JenkinsRule r = new JenkinsRule();
@Rule public RestartableJenkinsRule story = new RestartableJenkinsRule();

private static WeakReference<ClassLoader> LOADER;
public static void register(Object o) {
LOADER = new WeakReference<ClassLoader>(o.getClass().getClassLoader());
}
@Test public void loaderReleased() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(CpsFlowExecutionTest.class.getName() + ".register(this)"));
r.assertBuildStatusSuccess(p.scheduleBuild2(0));
assertNotNull(LOADER);
System.err.println(LOADER.get());
{
// TODO in Groovy 1.8.9 this keeps static state, but only for the last script (as also noted in JENKINS-23762).
// The fix of GROOVY-5025 (62bfb68) in 1.9 addresses this, which we would get if JENKINS-21249 is implemented.
Field f = ASTTransformationVisitor.class.getDeclaredField("compUnit");
f.setAccessible(true);
f.set(null, null);
}
MemoryAssert.assertGC(LOADER);
@Test public void loaderReleased() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(CpsFlowExecutionTest.class.getName() + ".register(this)"));
story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
assertNotNull(LOADER);
System.err.println(LOADER.get());
{
// TODO in Groovy 1.8.9 this keeps static state, but only for the last script (as also noted in JENKINS-23762).
// The fix of GROOVY-5025 (62bfb68) in 1.9 addresses this, which we would get if JENKINS-21249 is implemented.
Field f = ASTTransformationVisitor.class.getDeclaredField("compUnit");
f.setAccessible(true);
f.set(null, null);
}
MemoryAssert.assertGC(LOADER);
}
});
}

/* Failed attempt to make the test print soft references it has trouble clearing. The test ultimately passes, but cannot find the soft references via any root path.
Expand Down Expand Up @@ -95,4 +112,80 @@ private static void assertGC(WeakReference<?> reference) throws Exception {
}
*/

@Test public void getCurrentExecutions() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"echo 'a step'; semaphore 'one'; retry(2) {semaphore 'two'; node {semaphore 'three'}; semaphore 'four'}; semaphore 'five'; " +
"parallel a: {node {semaphore 'six'}}, b: {semaphore 'seven'}; semaphore 'eight'"));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("one/1", b);
FlowExecution e = b.getExecution();
assertStepExecutions(e, "semaphore");
SemaphoreStep.success("one/1", null);
SemaphoreStep.waitForStart("two/1", b);
assertStepExecutions(e, "retry {}", "semaphore");
SemaphoreStep.success("two/1", null);
SemaphoreStep.waitForStart("three/1", b);
assertStepExecutions(e, "retry {}", "node {}", "semaphore");
}
});
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.getItemByFullName("p", WorkflowJob.class);
WorkflowRun b = p.getLastBuild();
FlowExecution e = b.getExecution();
SemaphoreStep.success("three/1", null);
SemaphoreStep.waitForStart("four/1", b);
assertStepExecutions(e, "retry {}", "semaphore");
SemaphoreStep.failure("four/1", new AbortException("try again"));
SemaphoreStep.waitForStart("two/2", b);
assertStepExecutions(e, "retry {}", "semaphore");
SemaphoreStep.success("two/2", null);
SemaphoreStep.waitForStart("three/2", b);
assertStepExecutions(e, "retry {}", "node {}", "semaphore");
SemaphoreStep.success("three/2", null);
SemaphoreStep.waitForStart("four/2", b);
assertStepExecutions(e, "retry {}", "semaphore");
SemaphoreStep.success("four/2", null);
SemaphoreStep.waitForStart("five/1", b);
assertStepExecutions(e, "semaphore");
SemaphoreStep.success("five/1", null);
SemaphoreStep.waitForStart("six/1", b);
SemaphoreStep.waitForStart("seven/1", b);
assertStepExecutions(e, "parallel {}", "node {}", "semaphore", "semaphore");
SemaphoreStep.success("six/1", null);
SemaphoreStep.success("seven/1", null);
SemaphoreStep.waitForStart("eight/1", b);
assertStepExecutions(e, "semaphore");
SemaphoreStep.success("eight/1", null);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b));
assertStepExecutions(e);
}
});
}
private static void assertStepExecutions(FlowExecution e, String... steps) throws Exception {
List<String> current = stepNames(e.getCurrentExecutions(true));
List<String> all = stepNames(e.getCurrentExecutions(false));
int allCount = all.size();
int blockCount = allCount - current.size();
assertEquals(current + " was not the tail of " + all, current, all.subList(blockCount, allCount));
ListIterator<String> it = all.listIterator();
for (int i = 0; i < blockCount; i++) {
it.set(it.next() + " {}");
}
assertEquals(Arrays.toString(steps), all.toString());
}
private static List<String> stepNames(ListenableFuture<List<StepExecution>> executionsFuture) throws Exception {
List<String> r = new ArrayList<String>();
for (StepExecution e : executionsFuture.get()) {
// TODO should this method be defined in StepContext?
StepDescriptor d = ((CpsStepContext) e.getContext()).getStepDescriptor();
assertNotNull(d);
r.add(d.getFunctionName());
}
return r;
}

}
@@ -0,0 +1,115 @@
/*
* The MIT License
*
* Copyright 2015 CloudBees, Inc.
*
* 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.EnvVars;
import java.io.IOException;
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.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
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.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;

public class DynamicEnvironmentExpanderTest {

@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public RestartableJenkinsRule story = new RestartableJenkinsRule();

@Issue("JENKINS-26163")
@Test public void dynamics() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("dynamicEnv {echo \"initially ${env.DYNVAR}\"; semaphore 'wait'; echo \"subsequently ${env.DYNVAR}\"}"));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("wait/1", b);
story.j.waitForMessage("initially one", b);
}
});
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
SemaphoreStep.success("wait/1", null);
WorkflowJob p = story.j.jenkins.getItemByFullName("p", WorkflowJob.class);
WorkflowRun b = p.getLastBuild();
story.j.assertLogContains("subsequently two", story.j.waitForCompletion(b));
}
});
}
public static class DynamicEnvStep extends AbstractStepImpl {
@DataBoundConstructor public DynamicEnvStep() {}
public static class Execution extends AbstractStepExecutionImpl {
private static final long serialVersionUID = 1;
String value;
@Override public boolean start() throws Exception {
StepContext context = getContext();
value = "one";
context.newBodyInvoker().
withContexts(EnvironmentExpander.merge(context.get(EnvironmentExpander.class), new ExpanderImpl(this))).
withCallback(BodyExecutionCallback.wrap(context)).
withDisplayName(null).start();
return false;
}
@Override public void onResume() {
super.onResume();
value = "two";
}
@Override public void stop(Throwable cause) throws Exception {}
}
private static class ExpanderImpl extends EnvironmentExpander {
private static final long serialVersionUID = 1;
// Also works as this$0 from an inner class, but see http://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html#a4539 for why that is risky:
private final Execution execution;
ExpanderImpl(Execution execution) {
this.execution = execution;
}
@Override public void expand(EnvVars env) throws IOException, InterruptedException {
env.override("DYNVAR", execution.value);
}
}
@TestExtension("dynamics") public static class DescriptorImpl extends AbstractStepDescriptorImpl {
public DescriptorImpl() {
super(Execution.class);
}
@Override public String getFunctionName() {return "dynamicEnv";}
@Override public String getDisplayName() {return getFunctionName();}
@Override public boolean takesImplicitBlockArgument() {return true;}
}
}

}

0 comments on commit fb3e89a

Please sign in to comment.