package com.netflix.governator; import java.io.Closeable; import java.io.IOException; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.mockito.InOrder; import org.mockito.Mockito; import com.google.inject.AbstractModule; import com.google.inject.Key; import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.name.Named; import com.google.inject.name.Names; public class PreDestroyTest { private static final int GC_SLEEP_TIME = 100; private static class Foo { private volatile boolean shutdown = false; Foo() { System.out.println("Foo constructed: " + this); } @PreDestroy protected void shutdown() { shutdown = true; } public boolean isShutdown() { return shutdown; } @Override public String toString() { return "Foo@" + System.identityHashCode(this); } } @ThreadLocalScoped private static class AnnotatedFoo { private volatile boolean shutdown = false; @SuppressWarnings("unused") AnnotatedFoo() { System.out.println("AnnotatedFoo constructed: " + this); } @PreDestroy public void shutdown() { this.shutdown = true; } public boolean isShutdown() { return shutdown; } @Override public String toString() { return "AnnotatedFoo@" + System.identityHashCode(this); } } private static class InvalidPreDestroys { @PreDestroy public String shutdownWithReturnValue() { return "invalid return type"; } @PreDestroy public static void shutdownStatic() { // can't use static method type throw new RuntimeException("boom"); } @PreDestroy public void shutdownWithParameters(String invalidArg) { // can't use method parameters } } private interface PreDestroyInterface { @PreDestroy public void destroy(); } private static class PreDestroyImpl implements PreDestroyInterface { @Override public void destroy() { // should not be called } } private static class RunnableType implements Runnable { @Override @PreDestroy public void run() { // method from interface; will it be called? } } private static class CloseableType implements Closeable { @Override public void close() throws IOException { } @PreDestroy public void shutdown() { } } private static class PreDestroyParent1 { @PreDestroy public void shutdown() { } } private static class PreDestroyChild1 extends PreDestroyParent1 { @PreDestroy public void shutdown() { System.out.println("shutdown invoked"); } } private static class PreDestroyParent2 { @PreDestroy public void anotherShutdown() { } } private static class PreDestroyChild2 extends PreDestroyParent2 { @PreDestroy public void shutdown() { System.out.println("shutdown invoked"); } } private static class PreDestroyParent3 { @PreDestroy public void shutdown() { } } private static class PreDestroyChild3 extends PreDestroyParent3 { public void shutdown() { System.out.println("shutdown invoked"); } } private static class MultipleDestroys { @PreDestroy public void shutdown1() { System.out.println("shutdown1 invoked"); } @PreDestroy public void shutdown2() { System.out.println("shutdown2 invoked"); } } private static class EagerBean { volatile boolean shutdown = false; SingletonBean singletonInstance; @Inject public EagerBean(SingletonBean singletonInstance) { this.singletonInstance = singletonInstance; } @PreDestroy public void shutdown() { System.out.println("eager bean shutdown invoked"); shutdown = true; this.singletonInstance.eagerShutdown = true; } } @Singleton private static class SingletonBean { volatile boolean eagerShutdown = false; boolean shutdown = false; @PreDestroy public void shutdown() { System.out.println("singleton bean shutdown invoked"); shutdown = true; Assert.assertTrue(eagerShutdown); } } @Test public void testEagerSingletonShutdown() { EagerBean eagerBean; SingletonBean singletonBean; try (LifecycleInjector injector = InjectorBuilder.fromModule(new AbstractModule() { @Override protected void configure() { bind(EagerBean.class).asEagerSingleton(); bind(SingletonBean.class).in(Scopes.SINGLETON); }}).createInjector()) { eagerBean = injector.getInstance(EagerBean.class); singletonBean = injector.getInstance(SingletonBean.class); Assert.assertFalse(eagerBean.shutdown); Assert.assertFalse(singletonBean.shutdown); } Assert.assertTrue(eagerBean.shutdown); Assert.assertTrue(singletonBean.shutdown); } @Test public void testLifecycleShutdownInheritance1() { final PreDestroyChild1 preDestroyChild = Mockito.spy(new PreDestroyChild1()); InOrder inOrder = Mockito.inOrder(preDestroyChild); try (LifecycleInjector injector = TestSupport.inject(preDestroyChild)) { Assert.assertNotNull(injector.getInstance(preDestroyChild.getClass())); Mockito.verify(preDestroyChild, Mockito.never()).shutdown(); } // once not twice inOrder.verify(preDestroyChild, Mockito.times(1)).shutdown(); } @Test public void testLifecycleShutdownInheritance2() { final PreDestroyChild2 preDestroyChild = Mockito.spy(new PreDestroyChild2()); InOrder inOrder = Mockito.inOrder(preDestroyChild); try (LifecycleInjector injector = TestSupport.inject(preDestroyChild)) { Assert.assertNotNull(injector.getInstance(preDestroyChild.getClass())); Mockito.verify(preDestroyChild, Mockito.never()).shutdown(); } // once not twice inOrder.verify(preDestroyChild, Mockito.times(1)).shutdown(); inOrder.verify(preDestroyChild, Mockito.times(1)).anotherShutdown(); } @Test public void testLifecycleShutdownInheritance3() { final PreDestroyChild3 preDestroyChild = Mockito.spy(new PreDestroyChild3()); InOrder inOrder = Mockito.inOrder(preDestroyChild); try (LifecycleInjector injector = TestSupport.inject(preDestroyChild)) { Assert.assertNotNull(injector.getInstance(preDestroyChild.getClass())); Mockito.verify(preDestroyChild, Mockito.never()).shutdown(); } // never, child class overrides method without annotation inOrder.verify(preDestroyChild, Mockito.never()).shutdown(); } @Test public void testLifecycleMultipleAnnotations() { final MultipleDestroys multipleDestroys = Mockito.spy(new MultipleDestroys()); try (LifecycleInjector injector = new TestSupport() .withFeature(GovernatorFeatures.STRICT_JSR250_VALIDATION, true) .withSingleton(multipleDestroys) .inject()) { Assert.assertNotNull(injector.getInstance(multipleDestroys.getClass())); Mockito.verify(multipleDestroys, Mockito.never()).shutdown1(); Mockito.verify(multipleDestroys, Mockito.never()).shutdown2(); } // never, multiple annotations should be ignored Mockito.verify(multipleDestroys, Mockito.never()).shutdown1(); Mockito.verify(multipleDestroys, Mockito.never()).shutdown2(); } @Test public void testLifecycleDeclaredInterfaceMethod() { final RunnableType runnableInstance = Mockito.mock(RunnableType.class); InOrder inOrder = Mockito.inOrder(runnableInstance); try (LifecycleInjector injector = TestSupport.inject(runnableInstance)) { Assert.assertNotNull(injector.getInstance(RunnableType.class)); Mockito.verify(runnableInstance, Mockito.never()).run(); } inOrder.verify(runnableInstance, Mockito.times(1)).run(); } @Test public void testLifecycleAnnotatedInterfaceMethod() { final PreDestroyImpl impl = Mockito.mock(PreDestroyImpl.class); InOrder inOrder = Mockito.inOrder(impl); try (LifecycleInjector injector = TestSupport.inject(impl)) { Assert.assertNotNull(injector.getInstance(RunnableType.class)); Mockito.verify(impl, Mockito.never()).destroy(); } inOrder.verify(impl, Mockito.never()).destroy(); } @Test public void testLifecycleShutdownWithInvalidPreDestroys() { final InvalidPreDestroys ipd = Mockito.mock(InvalidPreDestroys.class); try (LifecycleInjector injector = new TestSupport() .withFeature(GovernatorFeatures.STRICT_JSR250_VALIDATION, true) .withSingleton(ipd) .inject()) { Assert.assertNotNull(injector.getInstance(InvalidPreDestroys.class)); Mockito.verify(ipd, Mockito.never()).shutdownWithParameters(Mockito.anyString()); Mockito.verify(ipd, Mockito.never()).shutdownWithReturnValue(); } Mockito.verify(ipd, Mockito.never()).shutdownWithParameters(Mockito.anyString()); Mockito.verify(ipd, Mockito.never()).shutdownWithReturnValue(); } @Test public void testLifecycleCloseable() { final CloseableType closeableType = Mockito.mock(CloseableType.class); try { Mockito.doThrow(new IOException("boom")).when(closeableType).close(); } catch (IOException e1) { // ignore, mock only } try (LifecycleInjector injector = TestSupport.inject(closeableType)) { Assert.assertNotNull(injector.getInstance(closeableType.getClass())); try { Mockito.verify(closeableType, Mockito.never()).close(); } catch (IOException e) { // close() called before shutdown and failed Assert.fail("close() called before shutdown and failed"); } } try { Mockito.verify(closeableType, Mockito.times(1)).close(); Mockito.verify(closeableType, Mockito.never()).shutdown(); } catch (IOException e) { // close() called before shutdown and failed Assert.fail("close() called after shutdown and failed"); } } @Test public void testLifecycleShutdown() { final Foo foo = Mockito.mock(Foo.class); try (LifecycleInjector injector = TestSupport.inject(foo)) { Assert.assertNotNull(injector.getInstance(foo.getClass())); Mockito.verify(foo, Mockito.never()).shutdown(); } Mockito.verify(foo, Mockito.times(1)).shutdown(); } @Test public void testLifecycleShutdownWithAtProvides() { InjectorBuilder builder = InjectorBuilder.fromModule(new AbstractModule() { @Override protected void configure() { } @Provides @Singleton Foo getFoo() { return new Foo(); } }); Foo managedFoo = null; try (LifecycleInjector injector = builder.createInjector()) { managedFoo = injector.getInstance(Foo.class); Assert.assertNotNull(managedFoo); Assert.assertFalse(managedFoo.isShutdown()); } managedFoo = null; builder = null; } @Test public void testLifecycleShutdownWithExplicitScope() throws Exception { final ThreadLocalScope threadLocalScope = new ThreadLocalScope(); InjectorBuilder builder = InjectorBuilder.fromModule(new AbstractModule() { @Override protected void configure() { binder().bind(Foo.class).in(threadLocalScope); } }); Foo managedFoo = null; try (LifecycleInjector injector = builder.createInjector()) { threadLocalScope.enter(); managedFoo = injector.getInstance(Foo.class); Assert.assertNotNull(managedFoo); Assert.assertFalse(managedFoo.isShutdown()); threadLocalScope.exit(); System.gc(); Thread.sleep(GC_SLEEP_TIME); Assert.assertTrue(managedFoo.isShutdown()); } } @Test public void testLifecycleShutdownWithAnnotatedExplicitScope() throws Exception { final ThreadLocalScope threadLocalScope = new ThreadLocalScope(); InjectorBuilder builder = InjectorBuilder.fromModules(new AbstractModule() { @Override protected void configure() { binder().bind(Key.get(AnnotatedFoo.class)); } }, new AbstractModule() { @Override protected void configure() { binder().bindScope(ThreadLocalScoped.class, threadLocalScope); } }); AnnotatedFoo managedFoo = null; try (LifecycleInjector injector = builder.createInjector()) { threadLocalScope.enter(); managedFoo = injector.getInstance(AnnotatedFoo.class); Assert.assertNotNull(managedFoo); Assert.assertFalse(managedFoo.shutdown); threadLocalScope.exit(); System.gc(); Thread.sleep(GC_SLEEP_TIME); synchronized(managedFoo) { Assert.assertTrue(managedFoo.shutdown); } } } @Test public void testLifecycleShutdownWithMultipleInScope() throws Exception { final ThreadLocalScope scope = new ThreadLocalScope(); InjectorBuilder builder = InjectorBuilder.fromModule(new AbstractModule() { @Override protected void configure() { binder().bindScope(ThreadLocalScoped.class, scope); } @Provides @ThreadLocalScoped @Named("afoo1") protected AnnotatedFoo afoo1() { return new AnnotatedFoo(); } @Provides @ThreadLocalScoped @Named("afoo2") protected AnnotatedFoo afoo2() { return new AnnotatedFoo(); } }); AnnotatedFoo managedFoo1 = null; AnnotatedFoo managedFoo2 = null; try (LifecycleInjector injector = builder.createInjector()) { scope.enter(); managedFoo1 = injector.getInstance(Key.get(AnnotatedFoo.class, Names.named("afoo1"))); Assert.assertNotNull(managedFoo1); Assert.assertFalse(managedFoo1.isShutdown()); managedFoo2 = injector.getInstance(Key.get(AnnotatedFoo.class, Names.named("afoo2"))); Assert.assertNotNull(managedFoo2); Assert.assertFalse(managedFoo2.isShutdown()); scope.exit(); System.gc(); Thread.sleep(GC_SLEEP_TIME); Assert.assertTrue(managedFoo1.isShutdown()); Assert.assertTrue(managedFoo2.isShutdown()); } } @Test public void testLifecycleShutdownWithSingletonScope() throws Exception { InjectorBuilder builder = InjectorBuilder.fromModule(new AbstractModule() { @Override protected void configure() { binder().bind(Foo.class).in(Scopes.SINGLETON); } }); Foo managedFoo = null; try (LifecycleInjector injector = builder.createInjector()) { managedFoo = injector.getInstance(Foo.class); Assert.assertNotNull(managedFoo); Assert.assertFalse(managedFoo.isShutdown()); } System.gc(); Thread.sleep(GC_SLEEP_TIME); Assert.assertTrue(managedFoo.isShutdown()); } @Before public void printTestHeader() { System.out.println("\n======================================================="); System.out.println(" Running Test : " + name.getMethodName()); System.out.println("=======================================================\n"); } @Rule public TestName name = new TestName(); }