View Javadoc
1   package com.atlassian.sal.core.lifecycle;
2   
3   import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
4   import com.atlassian.sal.api.lifecycle.LifecycleAware;
5   import com.google.common.collect.ImmutableList;
6   import org.junit.Test;
7   import org.junit.runner.RunWith;
8   import org.mockito.invocation.InvocationOnMock;
9   import org.mockito.junit.MockitoJUnitRunner;
10  import org.mockito.stubbing.Answer;
11  import org.osgi.framework.Bundle;
12  import org.osgi.framework.ServiceReference;
13  
14  import java.util.Collection;
15  
16  import static org.hamcrest.MatcherAssert.assertThat;
17  import static org.hamcrest.Matchers.is;
18  import static org.hamcrest.Matchers.notNullValue;
19  import static org.mockito.ArgumentMatchers.anyString;
20  import static org.mockito.ArgumentMatchers.same;
21  import static org.mockito.Mockito.doThrow;
22  import static org.mockito.Mockito.mock;
23  import static org.mockito.Mockito.never;
24  import static org.mockito.Mockito.verify;
25  import static org.mockito.Mockito.when;
26  
27  @RunWith(MockitoJUnitRunner.class)
28  public class TestDefaultLifecycleManager extends TestDefaultLifecycleManagerBase {
29      @Test
30      public void onStartCalledForServicesRegisteredBeforeDefaultLifecycleManagerIsInitialized() throws Exception {
31          final LifecycleAware lifecycleAware = mock(LifecycleAware.class);
32          final ServiceReference<LifecycleAware> service = registerService(pluginBundle, lifecycleAware);
33          when(bundleContext.getServiceReferences(LifecycleAware.class, null)).thenReturn(ImmutableList.of(service));
34  
35          defaultLifecycleManager.afterPropertiesSet();
36  
37          isApplicationSetup = true;
38          enablePlugin(pluginKey);
39  
40          // Should not be called yet, as things haven't started
41          verify(lifecycleAware, never()).onStart();
42  
43          defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
44  
45          // And now it should have started
46          verify(lifecycleAware).onStart();
47  
48          // And this is a convenient test to check resource management. This isn't perfect (we should check calls are balanced),
49          // but it's not bad and picks up the likely broken code cases.
50          verify(bundleContext).ungetService(service);
51      }
52  
53      @Test
54      public void onStartCalledWhenPluginFrameworkStartedIfApplicationIsSetup() throws Exception {
55          defaultLifecycleManager.afterPropertiesSet();
56  
57          final LifecycleAware lifecycleAware = mockLifecycleAwareAndRegisterService(pluginBundle);
58          enablePlugin(pluginKey);
59  
60          // Should not be called yet, as things haven't started
61          verify(lifecycleAware, never()).onStart();
62  
63          isApplicationSetup = true;
64          defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
65  
66          // And now it should have started
67          verify(lifecycleAware).onStart();
68      }
69  
70      @Test
71      public void onStartCalledWhenStartCalledIfApplicationIsSetupAfterPluginFrameworkStarted() throws Exception {
72          defaultLifecycleManager.afterPropertiesSet();
73  
74          final LifecycleAware lifecycleAware = mockLifecycleAwareAndRegisterService(pluginBundle);
75          enablePlugin(pluginKey);
76  
77          defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
78  
79          // Should not be called yet, as things haven't started
80          verify(lifecycleAware, never()).onStart();
81  
82          isApplicationSetup = true;
83          defaultLifecycleManager.start();
84  
85          // And now it should have started
86          verify(lifecycleAware).onStart();
87      }
88  
89      @Test
90      public void onStartCalledWhenPluginEnabledAfterStarted() throws Exception {
91          defaultLifecycleManager.afterPropertiesSet();
92  
93          final LifecycleAware lifecycleAware = mockLifecycleAwareAndRegisterService(pluginBundle);
94          defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
95          isApplicationSetup = true;
96          defaultLifecycleManager.start();
97  
98          // Should not be called yet, as plugin is not enabled
99          verify(lifecycleAware, never()).onStart();
100 
101         enablePlugin(pluginKey);
102 
103         // Should have been called now, in response to plugin being enabled
104         verify(lifecycleAware).onStart();
105     }
106 
107     @Test
108     public void onStartCalledWhenLifecycleAwareRegisteredAfterPluginEnabled() throws Exception {
109         defaultLifecycleManager.afterPropertiesSet();
110 
111         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
112         isApplicationSetup = true;
113         defaultLifecycleManager.start();
114 
115         enablePlugin(pluginKey);
116 
117         final LifecycleAware lifecycleAware = mockLifecycleAwareAndRegisterService(pluginBundle);
118         // Should have been called now, since the system is started and the plugin is enabled
119         verify(lifecycleAware).onStart();
120     }
121 
122     @Test
123     public void notifyOnStartCalledWhenPluginFrameworkStartedWhenSetup() throws Exception {
124         defaultLifecycleManager.afterPropertiesSet();
125 
126         isApplicationSetup = true;
127         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
128 
129         assertThat(notifyOnStartCalled, is(1));
130     }
131 
132     @Test
133     public void notifyOnStartCalledWhenStartCalledIfApplicationIsSetupAfterPluginFrameworkStarted() throws Exception {
134         defaultLifecycleManager.afterPropertiesSet();
135 
136         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
137 
138         // Not yet setup, so we should not have started yet
139         assertThat(notifyOnStartCalled, is(0));
140 
141         isApplicationSetup = true;
142         defaultLifecycleManager.start();
143 
144         assertThat(notifyOnStartCalled, is(1));
145     }
146 
147     @Test
148     public void notifyOnStartOnlyCalledOnceEvenWhenBothPluginFrameworkStartedAndStartCalledWhenSetup() throws Exception {
149         defaultLifecycleManager.afterPropertiesSet();
150 
151         isApplicationSetup = true;
152         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
153         defaultLifecycleManager.start();
154 
155         assertThat(notifyOnStartCalled, is(1));
156     }
157 
158     @Test
159     public void onStartNotCalledWhenLifecycleAwareUnregisteredBeforeStart() throws Exception {
160         defaultLifecycleManager.afterPropertiesSet();
161         // This is the shape you see if a plugin fails to enable but does get some services up before being disabled.
162         final LifecycleAware lifecycleAware = mock(LifecycleAware.class);
163         final ServiceReference serviceReference = registerService(pluginBundle, lifecycleAware);
164         unregisterService(serviceReference);
165 
166         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
167         isApplicationSetup = true;
168         defaultLifecycleManager.start();
169     }
170 
171     @Test
172     public void verifyListenerRegistrationManagement() throws Exception {
173         defaultLifecycleManager.afterPropertiesSet();
174 
175         assertThat(serviceListener, notNullValue());
176 
177         verify(pluginEventManager).register(defaultLifecycleManager);
178         // This is a little smoke and mirrors, in that we just captured it, but i want to keep this test orthogonal, and it serves
179         // to document the listener registration management also.
180         verify(bundleContext).addServiceListener(same(serviceListener), anyString());
181 
182 
183         verify(pluginEventManager, never()).unregister(defaultLifecycleManager);
184         verify(bundleContext, never()).removeServiceListener(serviceListener);
185 
186         defaultLifecycleManager.destroy();
187 
188         verify(pluginEventManager).unregister(defaultLifecycleManager);
189         verify(bundleContext).removeServiceListener(serviceListener);
190     }
191 
192     @Test
193     public void serviceDeregisteredJustAfterPostConstructSnapshotIsNotLeaked() throws Exception {
194         // This is a very greybox test, but i think there's logic worth exercising
195         final LifecycleAware lifecycleAware = mock(LifecycleAware.class);
196         final ServiceReference<LifecycleAware> serviceReference = registerService(pluginBundle, lifecycleAware);
197         // Arrange that just after DefaultLifecycleManager.postConstruct asks for the references, one of them goes away.
198         when(bundleContext.getServiceReferences(LifecycleAware.class, null)).thenAnswer(
199                 new Answer<Collection<ServiceReference<LifecycleAware>>>() {
200                     @Override
201                     public Collection<ServiceReference<LifecycleAware>> answer(final InvocationOnMock invocation)
202                             throws Throwable {
203                         // The idea is that BundleContext.getServiceReferences return the one service, and then it unregistered
204                         final ImmutableList<ServiceReference<LifecycleAware>> services = ImmutableList.of(serviceReference);
205                         unregisterService(serviceReference);
206                         return services;
207                     }
208                 });
209 
210         defaultLifecycleManager.afterPropertiesSet();
211 
212         // This is kind of a bit kludgy in terms of the test scenario, but it's the only really reasonable way to arrange we'd
213         // detect the failure we're looking for. The alternative is to add debug logging for this scenario and capture logging.
214         enablePlugin(pluginKey);
215         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
216         isApplicationSetup = true;
217         defaultLifecycleManager.start();
218 
219         // We should not start the lifecyleAware, because it wasn't registered by the time we started.
220         verify(lifecycleAware, never()).onStart();
221     }
222 
223     private void onStartThrowableIsCaught(Throwable t) throws Exception {
224         // It would be even better if we could test other instances of LifecycleAware are unaffected also, but without playing
225         // crazy brittle games on the HashSet ordering, i don't see a way to make such a test reliable.
226         defaultLifecycleManager.afterPropertiesSet();
227 
228         final LifecycleAware lifecycleAware = mock(LifecycleAware.class);
229         final ServiceReference<LifecycleAware> service = registerService(pluginBundle, lifecycleAware);
230         doThrow(t).when(lifecycleAware).onStart();
231         enablePlugin(pluginKey);
232 
233         // Should not be called yet, as things haven't started
234         verify(lifecycleAware, never()).onStart();
235 
236         isApplicationSetup = true;
237         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
238 
239         // And now it should have started
240         verify(lifecycleAware).onStart();
241         // Check resource management in the exception case also
242         verify(bundleContext).ungetService(service);
243     }
244 
245     @Test
246     public void onStartRuntimeExceptionsAreCaught() throws Exception {
247         onStartThrowableIsCaught(new RuntimeException("Test onStart failure"));
248     }
249 
250     @Test
251     public void onStartErrorsAreCaught() throws Exception {
252         onStartThrowableIsCaught(new Error("Test onStart failure"));
253     }
254 
255     @Test
256     public void staleServiceReferencesReturningNullBundleAndLifecycleAreHandled() throws Exception {
257         verifyStaleServiceReferencesAreHandled(true);
258     }
259 
260     @Test
261     public void staleServiceReferencesReturningNullLifecycleAreHandled() throws Exception {
262         verifyStaleServiceReferencesAreHandled(false);
263     }
264 
265     private void verifyStaleServiceReferencesAreHandled(final boolean makeBundleNull) throws Exception {
266         // This tests a race between DefaultLifecycleManager.notifyLifecycleAwareIfStartedAndEnabled() and service deregistration.
267         // It's not worth the effort of actually constructing the race, we can just rig return values ahead of time.
268         defaultLifecycleManager.afterPropertiesSet();
269 
270         final LifecycleAware lifecycleAware = mock(LifecycleAware.class);
271         final ServiceReference<LifecycleAware> service = registerService(pluginBundle, lifecycleAware);
272         enablePlugin(pluginKey);
273 
274         if (makeBundleNull) {
275             when(service.getBundle()).thenReturn(null);
276         }
277         when(bundleContext.getService(service)).thenReturn(null);
278 
279         isApplicationSetup = true;
280         defaultLifecycleManager.onPluginFrameworkStarted(new PluginFrameworkStartedEvent(pluginController, pluginAccessor));
281 
282         // In fact the majority of the verification here is that we don't throw, but it's worth checking a few details
283 
284         // We shouldn't have called onStart(). Hard to imagine how we could have, but i guess aggressive (broken) caching might.
285         verify(lifecycleAware, never()).onStart();
286         // We shouldn't unget in this case, because we didn't get successfully.
287         verify(bundleContext, never()).ungetService(service);
288     }
289 
290     private LifecycleAware mockLifecycleAwareAndRegisterService(final Bundle bundle) {
291         return mockLifecycleAwareAndRegisterService(bundle, LifecycleAware.class).left();
292     }
293 }