|
47 | 47 | import org.jvnet.hudson.test.JenkinsSessionRule;
|
48 | 48 |
|
49 | 49 | import groovy.lang.MissingMethodException;
|
| 50 | +import hudson.FilePath; |
50 | 51 | import hudson.Functions;
|
51 | 52 | import hudson.model.Result;
|
| 53 | +import hudson.model.TaskListener; |
52 | 54 | import hudson.remoting.ProxyException;
|
| 55 | +import hudson.remoting.VirtualChannel; |
| 56 | +import java.io.File; |
| 57 | +import java.io.IOException; |
| 58 | +import java.util.Set; |
| 59 | +import java.util.logging.Level; |
| 60 | +import jenkins.MasterToSlaveFileCallable; |
53 | 61 | import org.codehaus.groovy.runtime.NullObject;
|
| 62 | +import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; |
| 63 | +import org.jenkinsci.plugins.workflow.steps.Step; |
| 64 | +import org.jenkinsci.plugins.workflow.steps.StepContext; |
| 65 | +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; |
| 66 | +import org.jenkinsci.plugins.workflow.steps.StepExecution; |
| 67 | +import org.jenkinsci.plugins.workflow.steps.StepExecutions; |
| 68 | +import org.jvnet.hudson.test.InboundAgentRule; |
| 69 | +import org.jvnet.hudson.test.LoggerRule; |
| 70 | +import org.jvnet.hudson.test.TestExtension; |
| 71 | +import org.kohsuke.stapler.DataBoundConstructor; |
54 | 72 |
|
55 | 73 | /**
|
56 | 74 | * Tests for {@link ErrorAction}
|
57 | 75 | */
|
58 | 76 | public class ErrorActionTest {
|
| 77 | + |
59 | 78 | @ClassRule
|
60 | 79 | public static BuildWatcher buildWatcher = new BuildWatcher();
|
61 | 80 |
|
62 | 81 | @Rule
|
63 | 82 | public JenkinsSessionRule rr = new JenkinsSessionRule();
|
64 | 83 |
|
| 84 | + @Rule public InboundAgentRule agents = new InboundAgentRule(); |
| 85 | + |
| 86 | + @Rule public LoggerRule logging = new LoggerRule().record(ErrorAction.class, Level.FINE); |
| 87 | + |
65 | 88 | private List<ErrorAction> extractErrorActions(FlowExecution exec) {
|
66 | 89 | List<ErrorAction> ret = new ArrayList<>();
|
67 | 90 |
|
@@ -228,6 +251,87 @@ public static class X extends Exception {
|
228 | 251 | });
|
229 | 252 | }
|
230 | 253 |
|
| 254 | + @Test public void findOriginFromBodyExecutionCallback() throws Throwable { |
| 255 | + rr.then(r -> { |
| 256 | + agents.createAgent(r, "remote"); |
| 257 | + var p = r.createProject(WorkflowJob.class); |
| 258 | + p.setDefinition(new CpsFlowDefinition("callsFindOrigin {node('remote') {fails()}}", true)); |
| 259 | + var b = p.scheduleBuild2(0).waitForStart(); |
| 260 | + r.waitForMessage("Acting slowly in ", b); |
| 261 | + agents.stop("remote"); |
| 262 | + r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); |
| 263 | + r.assertLogContains("Found in: fails", b); |
| 264 | + }); |
| 265 | + } |
| 266 | + public static final class WrapperStep extends Step { |
| 267 | + @DataBoundConstructor public WrapperStep() {} |
| 268 | + @Override public StepExecution start(StepContext context) throws Exception { |
| 269 | + return new ExecutionImpl(context); |
| 270 | + } |
| 271 | + private static final class ExecutionImpl extends StepExecution { |
| 272 | + ExecutionImpl(StepContext context) { |
| 273 | + super(context); |
| 274 | + } |
| 275 | + @Override public boolean start() throws Exception { |
| 276 | + getContext().newBodyInvoker().withCallback(new Callback()).start(); |
| 277 | + return false; |
| 278 | + } |
| 279 | + } |
| 280 | + private static class Callback extends BodyExecutionCallback { |
| 281 | + @Override public void onSuccess(StepContext context, Object result) { |
| 282 | + context.onSuccess(result); |
| 283 | + } |
| 284 | + @Override |
| 285 | + public void onFailure(StepContext context, Throwable t) { |
| 286 | + try { |
| 287 | + var l = context.get(TaskListener.class); |
| 288 | + Functions.printStackTrace(t, l.error("Original failure:")); |
| 289 | + l.getLogger().println("Found in: " + ErrorAction.findOrigin(t, context.get(FlowExecution.class)).getDisplayFunctionName()); |
| 290 | + } catch (Exception x) { |
| 291 | + assert false : x; |
| 292 | + } |
| 293 | + context.onFailure(t); |
| 294 | + } |
| 295 | + } |
| 296 | + @TestExtension("findOriginFromBodyExecutionCallback") public static final class DescriptorImpl extends StepDescriptor { |
| 297 | + @Override public String getFunctionName() { |
| 298 | + return "callsFindOrigin"; |
| 299 | + } |
| 300 | + @Override public Set<? extends Class<?>> getRequiredContext() { |
| 301 | + return Set.of(); |
| 302 | + } |
| 303 | + @Override public boolean takesImplicitBlockArgument() { |
| 304 | + return true; |
| 305 | + } |
| 306 | + } |
| 307 | + } |
| 308 | + public static final class FailingStep extends Step { |
| 309 | + @DataBoundConstructor public FailingStep() {} |
| 310 | + @Override public StepExecution start(StepContext context) throws Exception { |
| 311 | + return StepExecutions.synchronousNonBlockingVoid(context, c -> c.get(FilePath.class).act(new Sleep(c.get(TaskListener.class)))); |
| 312 | + } |
| 313 | + private static final class Sleep extends MasterToSlaveFileCallable<Void> { |
| 314 | + private final TaskListener l; |
| 315 | + Sleep(TaskListener l) { |
| 316 | + this.l = l; |
| 317 | + } |
| 318 | + @Override public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { |
| 319 | + l.getLogger().println("Acting slowly in " + f); |
| 320 | + l.getLogger().flush(); |
| 321 | + Thread.sleep(Long.MAX_VALUE); |
| 322 | + return null; |
| 323 | + } |
| 324 | + } |
| 325 | + @TestExtension("findOriginFromBodyExecutionCallback") public static final class DescriptorImpl extends StepDescriptor { |
| 326 | + @Override public String getFunctionName() { |
| 327 | + return "fails"; |
| 328 | + } |
| 329 | + @Override public Set<? extends Class<?>> getRequiredContext() { |
| 330 | + return Set.of(FilePath.class); |
| 331 | + } |
| 332 | + } |
| 333 | + } |
| 334 | + |
231 | 335 | @Test public void cyclicErrorsAreSupported() throws Throwable {
|
232 | 336 | Exception cyclic1 = new Exception();
|
233 | 337 | Exception cyclic2 = new Exception(cyclic1);
|
|
0 commit comments