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.

Reply via email to