|
41 | 41 | import java.io.Serializable;
|
42 | 42 | import java.lang.ref.WeakReference;
|
43 | 43 | import java.util.Collections;
|
| 44 | +import java.util.Objects; |
44 | 45 | import java.util.HashMap;
|
45 | 46 | import java.util.Map;
|
46 | 47 | import java.util.Set;
|
47 | 48 | import java.util.concurrent.TimeUnit;
|
48 | 49 | import java.util.logging.Level;
|
| 50 | +import java.util.logging.LogRecord; |
| 51 | +import java.util.stream.Collectors; |
49 | 52 | import jenkins.model.Jenkins;
|
50 | 53 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
|
51 | 54 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
|
|
65 | 68 | import org.jvnet.hudson.test.JenkinsSessionRule;
|
66 | 69 | import org.jvnet.hudson.test.MemoryAssert;
|
67 | 70 | import org.jvnet.hudson.test.TestExtension;
|
| 71 | +import org.jvnet.hudson.test.recipes.LocalData; |
68 | 72 | import org.kohsuke.stapler.DataBoundConstructor;
|
69 | 73 |
|
70 | 74 | public class FlowExecutionListTest {
|
@@ -164,6 +168,35 @@ public class FlowExecutionListTest {
|
164 | 168 | });
|
165 | 169 | }
|
166 | 170 |
|
| 171 | + @LocalData |
| 172 | + @Test public void resumeStepExecutionsWithCorruptFlowGraphWithCycle() throws Throwable { |
| 173 | + // LocalData created using the following snippet while the build was waiting in the _second_ sleep, except |
| 174 | + // for build.xml, which was captured during the sleep step. The StepEndNode for the stage was then adjusted to |
| 175 | + // have its startId point to the timeout step's StepStartNode, creating a loop. |
| 176 | + /* |
| 177 | + sessions.then(r -> { |
| 178 | + var stuck = r.createProject(WorkflowJob.class); |
| 179 | + stuck.setDefinition(new CpsFlowDefinition("stage('stage') { sleep 30 }; timeout(time: 10) { sleep 30 }", true)); |
| 180 | + var b = stuck.scheduleBuild2(0).waitForStart(); |
| 181 | + System.out.println(b.getRootDir()); |
| 182 | + r.waitForCompletion(b); |
| 183 | + }); |
| 184 | + */ |
| 185 | + logging.capture(50); |
| 186 | + sessions.then(r -> { |
| 187 | + var p = r.jenkins.getItemByFullName("test0", WorkflowJob.class); |
| 188 | + var b = p.getBuildByNumber(1); |
| 189 | + r.waitForCompletion(b); |
| 190 | + assertThat(logging.getMessages(), hasItem(containsString("Unable to compute enclosing blocks"))); |
| 191 | + var loggedExceptions = logging.getRecords().stream() |
| 192 | + .map(LogRecord::getThrown) |
| 193 | + .filter(Objects::nonNull) |
| 194 | + .map(Throwable::toString) |
| 195 | + .collect(Collectors.toList()); |
| 196 | + assertThat(loggedExceptions, hasItem(containsString("Cycle in flow graph"))); |
| 197 | + }); |
| 198 | + } |
| 199 | + |
167 | 200 | @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() throws Throwable {
|
168 | 201 | sessions.then(r -> {
|
169 | 202 | var notStuck = r.createProject(WorkflowJob.class, "not-stuck");
|
|
0 commit comments