Revision: 1191
Author: sberlin
Date: Sat Jul 31 08:06:36 2010
Log: Make ProvisionException stack traces from @Provides methods clearer --
don't include all the intermediary RuntimeExceptions &
InvocationTargetExceptions. Just include the user's exception (even for
checked exceptions). Before this, a stack trace from a ProvisionException
looked like:
-----
1) Error in custom provider, java.lang.RuntimeException:
java.lang.reflect.InvocationTargetException
at
com.google.inject.ProvisionExceptionsTest$5.exploder(ProvisionExceptionsTest.java:134)
[while locating...]
1 error
at stack.to.where.injector.was.called
Caused by java.lang.RuntimeException:
java.lang.reflect.InvocationTargetException
at more.confusing.stacktraces
Caused by java.lang.reflect.InvocationTargetException
at more.confusing.stacktraces
Caused by UserException: UserExceptionMessage
at where.the.user.got.involved
-----
Now it looks like:
1) Error in custom provider, UserException: UserExceptionMessage
at
com.google.inject.ProvisionExceptionsTest$5.exploder(ProvisionExceptionsTest.java:134)
[while locating...]
1 error
at stack.to.where.injector.was.called
Caused by UserException: UserExceptionMessage
at stack.to.where.the.user.got.involved
http://code.google.com/p/google-guice/source/detail?r=1191
Added:
/trunk/src/com/google/inject/internal/Exceptions.java
/trunk/test/com/google/inject/ProvisionExceptionsTest.java
Modified:
/trunk/src/com/google/inject/internal/Errors.java
/trunk/src/com/google/inject/internal/ProviderMethod.java
=======================================
--- /dev/null
+++ /trunk/src/com/google/inject/internal/Exceptions.java Sat Jul 31
08:06:36 2010
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Rethrows user-code exceptions in wrapped exceptions so that Errors can
target the correct
+ * exception.
+ *
+ * @author [email protected] (Sam Berlin)
+ */
+class Exceptions {
+
+ /**
+ * Rethrows the exception (or it's cause) directly if possible. If it
was a checked exception,
+ * this wraps the exception in a stack trace with no frames, so that the
exception is shown
+ * immediately with no frames above it.
+ */
+ public static RuntimeException throwCleanly(InvocationTargetException
exception) {
+ Throwable cause = exception;
+ if(cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ if(cause instanceof RuntimeException) {
+ throw (RuntimeException)cause;
+ } else if(cause instanceof Error) {
+ throw (Error)cause;
+ } else {
+ throw new UnhandledCheckedUserException(cause);
+ }
+ }
+
+ /**
+ * A marker exception class that we look for in order to unwrap the
exception
+ * into the user exception, to provide a cleaner stack trace.
+ */
+ static class UnhandledCheckedUserException extends RuntimeException {
+ public UnhandledCheckedUserException(Throwable cause) {
+ super(cause);
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/test/com/google/inject/ProvisionExceptionsTest.java Sat Jul 31
08:06:36 2010
@@ -0,0 +1,178 @@
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+/**
+ * Tests that ProvisionExceptions are readable and clearly indicate to the
user what went wrong with
+ * their code.
+ *
+ * @author [email protected] (Sam Berlin)
+ */
+public class ProvisionExceptionsTest extends TestCase {
+
+ public void testConstructorRuntimeException() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindConstant().annotatedWith(Names.named("runtime")).to(true);
+ bind(Exploder.class).to(Explosion.class);
+ bind(Tracer.class).to(TracerImpl.class);
+ }
+ });
+ try {
+ injector.getInstance(Tracer.class);
+ fail();
+ } catch(ProvisionException pe) {
+ // Make sure our initial error message gives the user exception.
+ Asserts.assertContains(pe.getMessage(),
+ "1) Error injecting
constructor", "java.lang.IllegalStateException: boom!");
+ assertEquals(1, pe.getErrorMessages().size());
+ assertEquals(IllegalStateException.class, pe.getCause().getClass());
+ assertEquals(IllegalStateException.class,
Errors.getOnlyCause(pe.getErrorMessages()).getClass());
+ }
+ }
+
+ public void testConstructorCheckedException() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bindConstant().annotatedWith(Names.named("runtime")).to(false);
+ bind(Exploder.class).to(Explosion.class);
+ bind(Tracer.class).to(TracerImpl.class);
+ }
+ });
+ try {
+ injector.getInstance(Tracer.class);
+ fail();
+ } catch(ProvisionException pe) {
+ // Make sure our initial error message gives the user exception.
+ Asserts.assertContains(pe.getMessage(),
+ "1) Error injecting constructor", "java.io.IOException: boom!");
+ assertEquals(1, pe.getErrorMessages().size());
+ assertEquals(IOException.class, pe.getCause().getClass());
+ assertEquals(IOException.class,
Errors.getOnlyCause(pe.getErrorMessages()).getClass());
+ }
+ }
+
+ public void testCustomProvidersRuntimeException() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Exploder.class).toProvider(new Provider<Exploder>() {
+ public Exploder get() {
+ return Explosion.createRuntime();
+ }
+ });
+ bind(Tracer.class).to(TracerImpl.class);
+ }
+ });
+ try {
+ injector.getInstance(Tracer.class);
+ fail();
+ } catch(ProvisionException pe) {
+ // Make sure our initial error message gives the user exception.
+ Asserts.assertContains(pe.getMessage(),
+ "1) Error in custom provider", "java.lang.IllegalStateException:
boom!");
+ assertEquals(1, pe.getErrorMessages().size());
+ assertEquals(IllegalStateException.class, pe.getCause().getClass());
+ assertEquals(IllegalStateException.class,
Errors.getOnlyCause(pe.getErrorMessages()).getClass());
+ }
+ }
+
+ public void testProviderMethodRuntimeException() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Tracer.class).to(TracerImpl.class);
+ }
+ @Provides Exploder exploder() {
+ return Explosion.createRuntime();
+ }
+ });
+ try {
+ injector.getInstance(Tracer.class);
+ fail();
+ } catch(ProvisionException pe) {
+ // Make sure our initial error message gives the user exception.
+ Asserts.assertContains(pe.getMessage(),
+ "1) Error in custom provider", "java.lang.IllegalStateException:
boom!");
+ assertEquals(1, pe.getErrorMessages().size());
+ assertEquals(IllegalStateException.class, pe.getCause().getClass());
+ assertEquals(IllegalStateException.class,
Errors.getOnlyCause(pe.getErrorMessages()).getClass());
+ }
+ }
+
+ public void testProviderMethodCheckedException() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Tracer.class).to(TracerImpl.class);
+ }
+ @Provides Exploder exploder() throws IOException {
+ return Explosion.createChecked();
+ }
+ });
+ try {
+ injector.getInstance(Tracer.class);
+ fail();
+ } catch(ProvisionException pe) {
+ pe.printStackTrace();
+ // Make sure our initial error message gives the user exception.
+ Asserts.assertContains(pe.getMessage(),
+ "1) Error in custom provider", "java.io.IOException: boom!");
+ assertEquals(1, pe.getErrorMessages().size());
+ assertEquals(IOException.class, pe.getCause().getClass());
+ assertEquals(IOException.class,
Errors.getOnlyCause(pe.getErrorMessages()).getClass());
+ }
+ }
+
+ private static interface Exploder {}
+ public static class Explosion implements Exploder {
+ @Inject public Explosion(@Named("runtime") boolean runtime) throws
IOException {
+ if(runtime) {
+ throw new IllegalStateException("boom!");
+ } else {
+ throw new IOException("boom!");
+ }
+ }
+
+ public static Explosion createRuntime() {
+ try {
+ return new Explosion(true);
+ } catch(IOException iox) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static Explosion createChecked() throws IOException {
+ return new Explosion(false);
+ }
+ }
+ private static interface Tracer {}
+ private static class TracerImpl implements Tracer {
+ @Inject TracerImpl(Exploder explosion) {
+ }
+ }
+}
=======================================
--- /trunk/src/com/google/inject/internal/Errors.java Sat Jul 3 08:51:31
2010
+++ /trunk/src/com/google/inject/internal/Errors.java Sat Jul 31 08:06:36
2010
@@ -320,7 +320,8 @@
}
public Errors errorInProvider(RuntimeException runtimeException) {
- return errorInUserCode(runtimeException, "Error in custom
provider, %s", runtimeException);
+ Throwable unwrapped = unwrap(runtimeException);
+ return errorInUserCode(unwrapped, "Error in custom provider, %s",
unwrapped);
}
public Errors errorInUserInjector(
@@ -364,6 +365,14 @@
return addMessage(cause, messageFormat, arguments);
}
}
+
+ private Throwable unwrap(RuntimeException runtimeException) {
+ if(runtimeException instanceof
Exceptions.UnhandledCheckedUserException) {
+ return runtimeException.getCause();
+ } else {
+ return runtimeException;
+ }
+ }
public Errors cannotInjectRawProvider() {
return addMessage("Cannot inject a Provider that has no type
parameter");
=======================================
--- /trunk/src/com/google/inject/internal/ProviderMethod.java Sat Jul 3
08:51:31 2010
+++ /trunk/src/com/google/inject/internal/ProviderMethod.java Sat Jul 31
08:06:36 2010
@@ -105,7 +105,7 @@
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
- throw new RuntimeException(e);
+ throw Exceptions.throwCleanly(e);
}
}
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en.