1 /* 2 * Copyright (C) 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject; 18 19 import static com.google.inject.Asserts.assertContains; 20 import static com.google.inject.Asserts.getDeclaringSourcePart; 21 import static com.google.inject.Asserts.reserialize; 22 import static java.lang.annotation.ElementType.CONSTRUCTOR; 23 import static java.lang.annotation.ElementType.FIELD; 24 import static java.lang.annotation.ElementType.METHOD; 25 import static java.lang.annotation.ElementType.PARAMETER; 26 import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 28 import com.google.common.base.Throwables; 29 import com.google.inject.spi.Message; 30 import java.io.IOException; 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.Target; 33 import junit.framework.TestCase; 34 35 /** @author jessewilson@google.com (Jesse Wilson) */ 36 @SuppressWarnings("UnusedDeclaration") 37 public class ProvisionExceptionTest extends TestCase { 38 testExceptionsCollapsed()39 public void testExceptionsCollapsed() { 40 try { 41 Guice.createInjector().getInstance(A.class); 42 fail(); 43 } catch (ProvisionException e) { 44 assertTrue(e.getCause() instanceof UnsupportedOperationException); 45 assertContains( 46 e.getMessage(), 47 "Error injecting constructor", 48 "for the 1st parameter of com.google.inject.ProvisionExceptionTest$C.setD", 49 "for field at com.google.inject.ProvisionExceptionTest$B.c", 50 "for the 1st parameter of com.google.inject.ProvisionExceptionTest$A"); 51 } 52 } 53 54 /** 55 * There's a pass-through of user code in the scope. We want exceptions thrown by Guice to be 56 * limited to a single exception, even if it passes through user code. 57 */ testExceptionsCollapsedWithScopes()58 public void testExceptionsCollapsedWithScopes() { 59 try { 60 Guice.createInjector( 61 new AbstractModule() { 62 @Override 63 protected void configure() { 64 bind(B.class).in(Scopes.SINGLETON); 65 } 66 }) 67 .getInstance(A.class); 68 fail(); 69 } catch (ProvisionException e) { 70 assertTrue(e.getCause() instanceof UnsupportedOperationException); 71 assertFalse(e.getMessage().contains("custom provider")); 72 assertContains( 73 e.getMessage(), 74 "Error injecting constructor", 75 "for the 1st parameter of com.google.inject.ProvisionExceptionTest$C.setD", 76 "for field at com.google.inject.ProvisionExceptionTest$B.c", 77 "for the 1st parameter of com.google.inject.ProvisionExceptionTest$A"); 78 } 79 } 80 testMethodInjectionExceptions()81 public void testMethodInjectionExceptions() { 82 try { 83 Guice.createInjector().getInstance(E.class); 84 fail(); 85 } catch (ProvisionException e) { 86 assertTrue(e.getCause() instanceof UnsupportedOperationException); 87 assertContains( 88 e.getMessage(), 89 "Error injecting method", 90 "at " + E.class.getName() + ".setObject(ProvisionExceptionTest.java:"); 91 } 92 } 93 testBindToProviderInstanceExceptions()94 public void testBindToProviderInstanceExceptions() { 95 try { 96 Guice.createInjector( 97 new AbstractModule() { 98 @Override 99 protected void configure() { 100 bind(D.class).toProvider(new DProvider()); 101 } 102 }) 103 .getInstance(D.class); 104 fail(); 105 } catch (ProvisionException e) { 106 assertTrue(e.getCause() instanceof UnsupportedOperationException); 107 assertContains( 108 e.getMessage(), 109 "1) Error in custom provider, java.lang.UnsupportedOperationException", 110 "at " + ProvisionExceptionTest.class.getName(), 111 getDeclaringSourcePart(getClass())); 112 } 113 } 114 115 /** 116 * This test demonstrates that if the user throws a ProvisionException, we wrap it to add context. 117 */ testProvisionExceptionsAreWrappedForBindToType()118 public void testProvisionExceptionsAreWrappedForBindToType() { 119 try { 120 Guice.createInjector().getInstance(F.class); 121 fail(); 122 } catch (ProvisionException e) { 123 assertContains( 124 e.getMessage(), 125 "1) User Exception", 126 "at " + F.class.getName() + ".<init>(ProvisionExceptionTest.java:"); 127 } 128 } 129 testProvisionExceptionsAreWrappedForBindToProviderType()130 public void testProvisionExceptionsAreWrappedForBindToProviderType() { 131 try { 132 Guice.createInjector( 133 new AbstractModule() { 134 @Override 135 protected void configure() { 136 bind(F.class).toProvider(FProvider.class); 137 } 138 }) 139 .getInstance(F.class); 140 fail(); 141 } catch (ProvisionException e) { 142 assertContains( 143 e.getMessage(), 144 "1) User Exception", 145 "while locating ", 146 FProvider.class.getName(), 147 "while locating ", 148 F.class.getName()); 149 } 150 } 151 testProvisionExceptionsAreWrappedForBindToProviderInstance()152 public void testProvisionExceptionsAreWrappedForBindToProviderInstance() { 153 try { 154 Guice.createInjector( 155 new AbstractModule() { 156 @Override 157 protected void configure() { 158 bind(F.class).toProvider(new FProvider()); 159 } 160 }) 161 .getInstance(F.class); 162 fail(); 163 } catch (ProvisionException e) { 164 assertContains( 165 e.getMessage(), 166 "1) User Exception", 167 "at " + ProvisionExceptionTest.class.getName(), 168 getDeclaringSourcePart(getClass())); 169 } 170 } 171 testProvisionExceptionIsSerializable()172 public void testProvisionExceptionIsSerializable() throws IOException { 173 try { 174 Guice.createInjector().getInstance(A.class); 175 fail(); 176 } catch (ProvisionException expected) { 177 ProvisionException reserialized = reserialize(expected); 178 assertContains( 179 reserialized.getMessage(), 180 "1) Error injecting constructor, java.lang.UnsupportedOperationException", 181 "at com.google.inject.ProvisionExceptionTest$RealD.<init>()", 182 "at Key[type=com.google.inject.ProvisionExceptionTest$RealD, annotation=[none]]", 183 "@com.google.inject.ProvisionExceptionTest$C.setD()[0]", 184 "at Key[type=com.google.inject.ProvisionExceptionTest$C, annotation=[none]]", 185 "@com.google.inject.ProvisionExceptionTest$B.c", 186 "at Key[type=com.google.inject.ProvisionExceptionTest$B, annotation=[none]]", 187 "@com.google.inject.ProvisionExceptionTest$A.<init>()[0]", 188 "at Key[type=com.google.inject.ProvisionExceptionTest$A, annotation=[none]]"); 189 } 190 } 191 192 // The only way to trigger an exception with _multiple_ user controlled throwables is by 193 // triggering errors during injector creation. testMultipleCauses()194 public void testMultipleCauses() { 195 try { 196 Guice.createInjector( 197 Stage.PRODUCTION, 198 new AbstractModule() { 199 @Provides 200 @Singleton 201 String injectFirst() { 202 throw new IllegalArgumentException(new UnsupportedOperationException("Unsupported")); 203 } 204 205 @Provides 206 @Singleton 207 Object injectSecond() { 208 throw new NullPointerException("can't inject second either"); 209 } 210 }); 211 fail(); 212 } catch (CreationException e) { 213 assertContains( 214 e.getMessage(), 215 "1) Error in custom provider, java.lang.IllegalArgumentException", 216 "Caused by: java.lang.IllegalArgumentException: java.lang.UnsupportedOperationException", 217 "Caused by: java.lang.UnsupportedOperationException: Unsupported", 218 "2) Error in custom provider, java.lang.NullPointerException: can't inject second either", 219 "Caused by: java.lang.NullPointerException: can't inject second either", 220 "2 errors"); 221 } 222 } 223 testInjectInnerClass()224 public void testInjectInnerClass() throws Exception { 225 Injector injector = Guice.createInjector(); 226 try { 227 injector.getInstance(InnerClass.class); 228 fail(); 229 } catch (Exception expected) { 230 assertContains( 231 expected.getMessage(), 232 "Injecting into inner classes is not supported.", 233 "while locating " + InnerClass.class.getName()); 234 } 235 } 236 testInjectLocalClass()237 public void testInjectLocalClass() throws Exception { 238 class LocalClass {} 239 240 Injector injector = Guice.createInjector(); 241 try { 242 injector.getInstance(LocalClass.class); 243 fail(); 244 } catch (Exception expected) { 245 assertContains( 246 expected.getMessage(), 247 "Injecting into inner classes is not supported.", 248 "while locating " + LocalClass.class.getName()); 249 } 250 } 251 testBindingAnnotationsOnMethodsAndConstructors()252 public void testBindingAnnotationsOnMethodsAndConstructors() { 253 try { 254 Injector injector = Guice.createInjector(); 255 injector.getInstance(MethodWithBindingAnnotation.class); 256 fail(); 257 } catch (ConfigurationException expected) { 258 assertContains( 259 expected.getMessage(), 260 MethodWithBindingAnnotation.class.getName() + ".injectMe() is annotated with @", 261 Green.class.getName() + "(), ", 262 "but binding annotations should be applied to its parameters instead.", 263 "while locating " + MethodWithBindingAnnotation.class.getName()); 264 } 265 266 try { 267 Guice.createInjector().getInstance(ConstructorWithBindingAnnotation.class); 268 fail(); 269 } catch (ConfigurationException expected) { 270 assertContains( 271 expected.getMessage(), 272 ConstructorWithBindingAnnotation.class.getName() + ".<init>() is annotated with @", 273 Green.class.getName() + "(), ", 274 "but binding annotations should be applied to its parameters instead.", 275 "at " + ConstructorWithBindingAnnotation.class.getName() + ".class", 276 "while locating " + ConstructorWithBindingAnnotation.class.getName()); 277 } 278 } 279 testBindingAnnotationWarningForScala()280 public void testBindingAnnotationWarningForScala() { 281 Injector injector = 282 Guice.createInjector( 283 new AbstractModule() { 284 @Override 285 protected void configure() { 286 bind(String.class).annotatedWith(Green.class).toInstance("lime!"); 287 } 288 }); 289 injector.getInstance(LikeScala.class); 290 } 291 testLinkedBindings()292 public void testLinkedBindings() { 293 Injector injector = 294 Guice.createInjector( 295 new AbstractModule() { 296 @Override 297 protected void configure() { 298 bind(D.class).to(RealD.class); 299 } 300 }); 301 302 try { 303 injector.getInstance(D.class); 304 fail(); 305 } catch (ProvisionException expected) { 306 assertContains( 307 expected.getMessage(), 308 "at " + RealD.class.getName() + ".<init>(ProvisionExceptionTest.java:", 309 "while locating " + RealD.class.getName(), 310 "while locating " + D.class.getName()); 311 } 312 } 313 testProviderKeyBindings()314 public void testProviderKeyBindings() { 315 Injector injector = 316 Guice.createInjector( 317 new AbstractModule() { 318 @Override 319 protected void configure() { 320 bind(D.class).toProvider(DProvider.class); 321 } 322 }); 323 324 try { 325 injector.getInstance(D.class); 326 fail(); 327 } catch (ProvisionException expected) { 328 assertContains( 329 expected.getMessage(), 330 "while locating " + DProvider.class.getName(), 331 "while locating " + D.class.getName()); 332 } 333 } 334 testDuplicateCausesCollapsed()335 public void testDuplicateCausesCollapsed() { 336 final RuntimeException sharedException = new RuntimeException("fail"); 337 try { 338 Guice.createInjector( 339 new AbstractModule() { 340 @Override 341 protected void configure() { 342 addError(sharedException); 343 addError(sharedException); 344 } 345 }); 346 fail(); 347 } catch (CreationException ce) { 348 assertEquals(sharedException, ce.getCause()); 349 assertEquals(2, ce.getErrorMessages().size()); 350 for (Message message : ce.getErrorMessages()) { 351 assertEquals(sharedException, message.getCause()); 352 } 353 } 354 } 355 testMultipleDuplicates()356 public void testMultipleDuplicates() { 357 final RuntimeException exception1 = new RuntimeException("fail"); 358 final RuntimeException exception2 = new RuntimeException("abort"); 359 try { 360 Guice.createInjector( 361 new AbstractModule() { 362 @Override 363 protected void configure() { 364 addError(exception1); 365 addError(exception1); 366 addError(exception2); 367 addError(exception2); 368 } 369 }); 370 fail(); 371 } catch (CreationException ce) { 372 assertNull(ce.getCause()); 373 assertEquals(4, ce.getErrorMessages().size()); 374 375 String e1 = Throwables.getStackTraceAsString(exception1); 376 String e2 = Throwables.getStackTraceAsString(exception2); 377 assertContains( 378 ce.getMessage(), 379 "\n1) ", 380 e1, 381 "\n2) ", 382 "(same stack trace as error #1)", 383 "\n3) ", 384 e2, 385 "\n4) ", 386 "(same stack trace as error #3)"); 387 } 388 } 389 390 @SuppressWarnings("ClassCanBeStatic") 391 private class InnerClass {} 392 393 static class A { 394 @Inject A(B b)395 A(B b) {} 396 } 397 398 static class B { 399 @Inject C c; 400 } 401 402 static class C { 403 @Inject setD(RealD d)404 void setD(RealD d) {} 405 } 406 407 static class E { 408 @Inject setObject(Object o)409 void setObject(Object o) { 410 throw new UnsupportedOperationException(); 411 } 412 } 413 414 static class MethodWithBindingAnnotation { 415 @Inject 416 @Green injectMe(String greenString)417 void injectMe(String greenString) {} 418 } 419 420 static class ConstructorWithBindingAnnotation { 421 // Suppress compiler errors by the error-prone checker InjectedConstructorAnnotations, 422 // which catches injected constructors with binding annotations. 423 @SuppressWarnings("InjectedConstructorAnnotations") 424 @Inject 425 @Green ConstructorWithBindingAnnotation(String greenString)426 ConstructorWithBindingAnnotation(String greenString) {} 427 } 428 429 /** 430 * In Scala, fields automatically get accessor methods with the same name. So we don't do 431 * misplaced-binding annotation detection if the offending method has a matching field. 432 */ 433 static class LikeScala { 434 @Inject @Green String green; 435 436 @Inject 437 @Green green()438 String green() { 439 return green; 440 } 441 } 442 443 @Retention(RUNTIME) 444 @Target({FIELD, PARAMETER, CONSTRUCTOR, METHOD}) 445 @BindingAnnotation 446 @interface Green {} 447 448 interface D {} 449 450 static class RealD implements D { 451 @Inject RealD()452 RealD() { 453 throw new UnsupportedOperationException(); 454 } 455 } 456 457 static class DProvider implements Provider<D> { 458 @Override get()459 public D get() { 460 throw new UnsupportedOperationException(); 461 } 462 } 463 464 static class F { 465 @Inject F()466 public F() { 467 throw new ProvisionException("User Exception", new RuntimeException()); 468 } 469 } 470 471 static class FProvider implements Provider<F> { 472 @Override get()473 public F get() { 474 return new F(); 475 } 476 } 477 } 478