package org.jenkinsci.plugins.workflow.cps;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.cps.CpsThreadDump.ThreadInfo;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
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.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.JenkinsRule;
public class CpsThreadDumpTest {
@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public JenkinsRule j = new JenkinsRule();
private WorkflowJob p;
@Before
public void setUp() throws Exception {
p = j.jenkins.createProject(WorkflowJob.class, "p");
}
@Test
public void simple() throws Exception {
p.setDefinition(new CpsFlowDefinition(StringUtils.join(asList(
"def foo() { bar() }",
"def bar() {",
" semaphore 'x'",
"}",
"foo()"
), "\n")));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("x/1", b);
CpsThreadDumpAction action = b.getAction(CpsThreadDumpAction.class);
if (action == null) {
FlowExecutionOwner owner = b.asFlowExecutionOwner();
assertNotNull(owner);
CpsFlowExecution exec = (CpsFlowExecution) owner.getOrNull();
assertNotNull(exec);
assertFalse(exec.isComplete());
fail("not sure why CpsThreadDumpAction was missing here");
}
CpsThreadDump td = action.threadDumpSynchronous();
td.print(System.out);
{// verify that we got the right thread dump
List<ThreadInfo> threads = td.getThreads();
assertEquals(1, threads.size());
ThreadInfo t = threads.get(0);
assertEquals("Thread #0", t.getHeadline());
assertStackTrace(t,
"DSL.semaphore(waiting on x/1)",
"WorkflowScript.bar(WorkflowScript:3)",
"WorkflowScript.foo(WorkflowScript:1)",
"WorkflowScript.run(WorkflowScript:5)");
}
SemaphoreStep.success("x/1", null);
j.waitForCompletion(b);
assertNull(b.getAction(CpsThreadDumpAction.class));
}
@Test
public void parallel() throws Exception {
p.setDefinition(new CpsFlowDefinition(StringUtils.join(asList(
"def foo(x) { bar(x) }",// 1
"def bar(x) {",
" semaphore x", // 3
"}",
"def zot() {",
" parallel(", // 6
" b1:{ foo('x') },",
" b2:{ bar('y') });",
"}",
"zot()" // 10
), "\n")));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("x/1", b);
SemaphoreStep.waitForStart("y/1", b);
CpsThreadDump td = b.getAction(CpsThreadDumpAction.class).threadDumpSynchronous();
td.print(System.out);
assertStackTrace( td.getThreads().get(0),
"DSL.semaphore(waiting on x/1)",
"WorkflowScript.bar(WorkflowScript:3)",
"WorkflowScript.foo(WorkflowScript:1)",
"WorkflowScript.zot(WorkflowScript:7)",
"DSL.parallel(Native Method)",
"WorkflowScript.zot(WorkflowScript:6)",
"WorkflowScript.run(WorkflowScript:10)");
assertStackTrace( td.getThreads().get(1),
"DSL.semaphore(waiting on y/1)",
"WorkflowScript.bar(WorkflowScript:3)",
"WorkflowScript.zot(WorkflowScript:8)");
}
@Test public void load() throws Exception {
j.jenkins.getWorkspaceFor(p).child("lib.groovy").write("def m() {semaphore 'here'}; this", null);
p.setDefinition(new CpsFlowDefinition("def lib; node {lib = load 'lib.groovy'}; lib.m()", true));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("here/1", b);
CpsThreadDump td = b.getAction(CpsThreadDumpAction.class).threadDumpSynchronous();
td.print(System.out);
assertStackTrace(td.getThreads().get(0),
"DSL.semaphore(waiting on here/1)",
"Script1.m(Script1.groovy:1)",
"WorkflowScript.run(WorkflowScript:1)");
}
@Test public void nativeMethods() throws Exception {
p.setDefinition(new CpsFlowDefinition(
"@NonCPS def untransformed() {Thread.sleep(Long.MAX_VALUE)}\n" +
"def helper() {echo 'sleeping'; /* flush output */ sleep 1; untransformed()}\n" +
"helper()"));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
CpsFlowExecution e = (CpsFlowExecution) b.getExecutionPromise().get();
j.waitForMessage("sleeping", b);
do { // wait for the CPS VM to be busy (opposite of waitForSuspension)
Thread.sleep(100);
} while (!e.blocksRestart());
CpsThreadDump td = e.getThreadDump();
td.print(System.out);
assertStackTrace(td.getThreads().get(0),
// TODO would like to see untransformed and Thread.sleep here
"WorkflowScript.helper(WorkflowScript:2)",
"WorkflowScript.run(WorkflowScript:3)");
b.doKill();
}
private void assertStackTrace(ThreadInfo t, String... expected) {
assertEquals(asList(expected), toString(t.getStackTrace()));
}
private List<String> toString(List<StackTraceElement> in) {
List<String> r = new ArrayList<String>();
for (StackTraceElement e : in)
r.add(e.toString());
return r;
}
}