diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index 66a2ff90e73adf88a6864fb50409dfba8dc29a08..48d3af0284be30222857d7c583ce78fbb051bb79 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -1144,7 +1144,7 @@ public abstract class Descriptor> implements Saveable { .generateResponse(req, rsp, node); } else { // for now, we can't really use the field name that caused the problem. - new Failure(getMessage()).generateResponse(req,rsp,node); + new Failure(getMessage()).generateResponse(req,rsp,node,getCause()); } } } diff --git a/core/src/main/java/hudson/model/Failure.java b/core/src/main/java/hudson/model/Failure.java index fa948901997dc6930271206810b7ffa0da30d83f..70f3e3844f409c659b544936ee527ac0b85bafdc 100644 --- a/core/src/main/java/hudson/model/Failure.java +++ b/core/src/main/java/hudson/model/Failure.java @@ -28,6 +28,7 @@ import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; +import javax.annotation.Nullable; import javax.servlet.ServletException; import java.io.IOException; @@ -54,6 +55,13 @@ public class Failure extends RuntimeException implements HttpResponse { this.pre = pre; } + public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node, @Nullable Throwable throwable) throws IOException, ServletException { + if (throwable != null) { + req.setAttribute("exception", throwable); + } + generateResponse(req, rsp, node); + } + public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException { req.setAttribute("message",getMessage()); if(pre) diff --git a/test/src/test/java/hudson/model/DescriptorTest.java b/test/src/test/java/hudson/model/DescriptorTest.java index ef669bef1bc918b083cc9b420ab67fb96ab0c04c..4182f826c4289b60e87159eb73ec844c0e1f0222 100644 --- a/test/src/test/java/hudson/model/DescriptorTest.java +++ b/test/src/test/java/hudson/model/DescriptorTest.java @@ -24,6 +24,7 @@ package hudson.model; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import hudson.Launcher; import hudson.model.Descriptor.PropertyType; import hudson.tasks.BuildStepDescriptor; @@ -32,9 +33,14 @@ import hudson.tasks.Shell; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Callable; + import jenkins.model.Jenkins; import net.sf.json.JSONObject; + +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; + import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -42,6 +48,7 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; @SuppressWarnings({"unchecked", "rawtypes"}) @@ -195,4 +202,23 @@ public class DescriptorTest { @TestExtension("nestedDescribableSharingClass") public static class DescriptorImpl extends Descriptor {} } + @Test + public void presentStacktraceFromFormException() throws Exception { + NullPointerException cause = new NullPointerException(); + final Descriptor.FormException fe = new Descriptor.FormException("My Message", cause, "fake"); + try { + rule.executeOnServer(new Callable() { + @Override public Void call() throws Exception { + fe.generateResponse(Stapler.getCurrentRequest(), Stapler.getCurrentResponse(), Jenkins.getInstance()); + return null; + } + }); + fail(); + } catch (FailingHttpStatusCodeException ex) { + String response = ex.getResponse().getContentAsString(); + assertThat(response, containsString(fe.getMessage())); + assertThat(response, containsString(cause.getClass().getCanonicalName())); + assertThat(response, containsString(getClass().getCanonicalName())); + } + } }