View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.MockModuleDescriptor;
4   import com.atlassian.plugin.ModuleDescriptor;
5   import com.atlassian.plugin.Plugin;
6   import com.atlassian.plugin.PluginAccessor;
7   import com.atlassian.plugin.PluginController;
8   import com.atlassian.plugin.event.PluginEventManager;
9   import com.atlassian.plugin.event.events.PluginDisabledEvent;
10  import com.atlassian.plugin.event.events.PluginEnabledEvent;
11  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
12  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
13  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
14  import com.google.common.collect.ImmutableList;
15  import com.google.common.collect.Iterators;
16  import com.google.common.collect.Lists;
17  import org.junit.Before;
18  import org.junit.Rule;
19  import org.junit.Test;
20  import org.mockito.Mock;
21  import org.mockito.junit.MockitoJUnit;
22  import org.mockito.junit.MockitoRule;
23  
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import static com.google.common.collect.Lists.newArrayList;
29  import static java.util.Collections.singletonList;
30  import static org.hamcrest.Matchers.contains;
31  import static org.hamcrest.Matchers.containsInAnyOrder;
32  import static org.hamcrest.Matchers.empty;
33  import static org.hamcrest.Matchers.sameInstance;
34  import static org.junit.Assert.assertEquals;
35  import static org.junit.Assert.assertFalse;
36  import static org.junit.Assert.assertSame;
37  import static org.junit.Assert.assertThat;
38  import static org.mockito.Matchers.any;
39  import static org.mockito.Mockito.mock;
40  import static org.mockito.Mockito.never;
41  import static org.mockito.Mockito.times;
42  import static org.mockito.Mockito.verify;
43  import static org.mockito.Mockito.when;
44  
45  /**
46   * Mock-based unit tests of {@link EnabledModuleCachingPluginAccessor}.
47   *
48   * @see TestDefaultPluginManagerWithCachingPluginAccessor for integration test with {@link DefaultPluginManager}.
49   */
50  public class TestEnabledModuleCachingPluginAccessor {
51      private PluginEventManager pluginEventManager;
52  
53      @Rule
54      public MockitoRule mockitoRule = MockitoJUnit.rule();
55  
56      @Mock
57      private PluginAccessor delegate;
58  
59      @Mock
60      private PluginController pluginController;
61  
62      private PluginAccessor cachingPluginAccessor;
63  
64      @Before
65      public void setUp() throws Exception {
66          pluginEventManager = new DefaultPluginEventManager();
67          cachingPluginAccessor = new EnabledModuleCachingPluginAccessor(delegate, pluginEventManager, pluginController);
68      }
69  
70      @Test
71      public void testGetEnabledModuleDescriptorsByClassDelegateShouldCalculateAtMostOnce() {
72          // call the cached method multiple times.
73          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
74          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
75          // should have been called only once
76          verify(delegate, times(1)).getEnabledModuleDescriptorsByClass(Descriptor.class);
77  
78          // broadcast new module and then call again multiple times
79          pluginEventManager.broadcast(new PluginModuleEnabledEvent(new Descriptor(new Module())));
80          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
81          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
82  
83          // should have been called only once more (because the cache was cleared and re-loaded following enabled event)
84          verify(delegate, times(2)).getEnabledModuleDescriptorsByClass(Descriptor.class);
85      }
86  
87      @Test
88      public void testGetEnabledModuleDescriptorsByClassFlushCacheAfterAnyPluginDisable() {
89          // call the cached method - populates cache
90          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
91          verify(delegate, times(1)).getEnabledModuleDescriptorsByClass(Descriptor.class);
92  
93          pluginEventManager.broadcast(new PluginDisabledEvent(mock(Plugin.class)));
94          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
95          cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
96  
97          // Called again (but only once more) following cache flush
98          verify(delegate, times(2)).getEnabledModuleDescriptorsByClass(Descriptor.class);
99      }
100 
101     @Test
102     public void testGetEnabledModuleDescriptorsByClassFlushCacheAfterAnyPluginEnable() {
103         // call the cached method - populates cache
104         cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
105         verify(delegate, times(1)).getEnabledModuleDescriptorsByClass(Descriptor.class);
106 
107         pluginEventManager.broadcast(new PluginEnabledEvent(mock(Plugin.class)));
108         cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
109         cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
110 
111         // Called again (but only once more) following cache flush
112         verify(delegate, times(2)).getEnabledModuleDescriptorsByClass(Descriptor.class);
113     }
114 
115     @Test
116     public void testGetEnabledModuleDescriptorsByClassChildAndParentClassBeingTrackedSeparately() {
117         final ChildDescriptor<ChildModule> childDescriptor = new ChildDescriptor<>(new ChildModule());
118         final ParentDescriptor<ParentModule> parentDescriptor = new ParentDescriptor<>(new ParentModule());
119 
120         when(delegate.getEnabledModuleDescriptorsByClass(ChildDescriptor.class))
121                 .thenReturn(Collections.<ChildDescriptor>singletonList(childDescriptor));
122         when(delegate.getEnabledModuleDescriptorsByClass(ParentDescriptor.class))
123                 .thenReturn(Lists.<ParentDescriptor>newArrayList(parentDescriptor, childDescriptor));
124 
125         final List<ChildDescriptor> result = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ChildDescriptor.class);
126         assertEquals(1, result.size());
127         assertSame(childDescriptor, result.get(0));
128 
129         final List<ParentDescriptor> result2 = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ParentDescriptor.class);
130         // (gave up on trying to get generics to work with 'contains/containsInAnyOrder')
131         assertEquals(2, result2.size());
132         assertEquals(parentDescriptor, result2.get(0));
133         assertEquals(childDescriptor, result2.get(1));
134     }
135 
136     @Test
137     public void testGetEnabledModuleDescriptorsByClassFindsLateChild() {
138         final ChildDescriptor childDescriptor = new ChildDescriptor<>(new ChildModule());
139         final ParentDescriptor parentDescriptor = new ParentDescriptor<>(new ParentModule());
140 
141         when(delegate.getEnabledModuleDescriptorsByClass(ChildDescriptor.class)).thenReturn(ImmutableList.<ChildDescriptor>of());
142         when(delegate.getEnabledModuleDescriptorsByClass(ParentDescriptor.class)).thenReturn(ImmutableList.of(parentDescriptor));
143 
144         final List<ChildDescriptor> childBefore = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ChildDescriptor.class);
145         final List<ParentDescriptor> parentBefore = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ParentDescriptor.class);
146 
147         assertThat(childBefore, empty());
148         assertThat(parentBefore, contains(parentDescriptor));
149 
150         // Now enable child
151 
152         when(delegate.getEnabledModuleDescriptorsByClass(ChildDescriptor.class)).thenReturn(ImmutableList.of(childDescriptor));
153         when(delegate.getEnabledModuleDescriptorsByClass(ParentDescriptor.class)).
154                 thenReturn(ImmutableList.of(parentDescriptor, childDescriptor));
155 
156         pluginEventManager.broadcast(new PluginModuleEnabledEvent(childDescriptor));
157 
158         final List<ChildDescriptor> childAfter = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ChildDescriptor.class);
159         final List<ParentDescriptor> parentAfter = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(ParentDescriptor.class);
160 
161         assertThat(childAfter, contains(childDescriptor));
162         assertThat(parentAfter, contains(parentDescriptor, childDescriptor));
163     }
164 
165     @Test
166     public void testGetEnabledModuleDescriptorsByClassWhenModuleEnabledBeforeCacheAccessed() {
167         final Descriptor descriptor = new Descriptor(new Module());
168 
169         // Mock for cache loader as if module already enabled
170         when(delegate.getEnabledModuleDescriptorsByClass(Descriptor.class)).thenReturn(singletonList(descriptor));
171 
172         // Cache loader should see the module
173         List<Descriptor> result = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
174         assertThat(result, contains(descriptor));
175     }
176 
177     @Test
178     public void testGetEnabledModuleDescriptorsByClassWhenModuleEnabledAfterCacheAccessed() {
179         final Descriptor descriptor = new Descriptor(new Module());
180 
181         // Mock for cache loader as if module initially disabled
182         when(delegate.getEnabledModuleDescriptorsByClass(Descriptor.class)).thenReturn(Collections.<Descriptor>emptyList());
183 
184         // Cache loader
185         List<Descriptor> result = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
186         assertThat(result, empty());
187 
188         // Now gets enabled - should see the event and reload the cache
189         when(delegate.getEnabledModuleDescriptorsByClass(Descriptor.class)).thenReturn(singletonList(descriptor));
190         pluginEventManager.broadcast(new PluginModuleEnabledEvent(descriptor));
191 
192         result = cachingPluginAccessor.getEnabledModuleDescriptorsByClass(Descriptor.class);
193         assertThat(result, contains(descriptor));
194     }
195 
196     @Test
197     public void testGetEnabledModulesByClassDelegateShouldCalculateAtMostOnce() {
198         // call the cached method multiple times.
199         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
200         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
201 
202         // Cache loading of ByModuleClassCacheLoader works via a predicate across all plugins' modules,
203         // not by calling the delegate: see EnabledModuleCachingPluginAccessor.getEnabledModuleDescriptorsByModuleClass()
204         verify(delegate, times(1)).getEnabledPlugins();
205         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
206 
207         // broadcast new module and then call again.
208         pluginEventManager.broadcast(new PluginModuleEnabledEvent(new Descriptor(new Module())));
209         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
210         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
211 
212         // Only one more call through to delegate (due to the enabled event clearing the cache entry)
213         verify(delegate, times(2)).getEnabledPlugins();
214         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
215     }
216 
217     @Test
218     public void testGetEnabledModulesByClassFlushCacheAfterAnyPluginEnable() {
219         // call the cached method - populates cache
220         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
221 
222         // Cache loading of ByModuleClassCacheLoader works via a predicate across all plugins' modules,
223         // not by calling the delegate: see EnabledModuleCachingPluginAccessor.getEnabledModuleDescriptorsByModuleClass()
224         verify(delegate, times(1)).getEnabledPlugins();
225         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
226 
227         pluginEventManager.broadcast(new PluginEnabledEvent(mock(Plugin.class)));
228         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
229         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
230 
231         // Cache has been cleared, now gets loaded again
232         verify(delegate, times(2)).getEnabledPlugins();
233         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
234     }
235 
236     @Test
237     public void testGetEnabledModulesByClassFlushCacheAfterAnyPluginDisable() {
238         // call the cached method - populates cache
239         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
240 
241         // Cache loading of ByModuleClassCacheLoader works via a predicate across all plugins' modules,
242         // not by calling the delegate: see EnabledModuleCachingPluginAccessor.getEnabledModuleDescriptorsByModuleClass()
243         verify(delegate, times(1)).getEnabledPlugins();
244         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
245 
246         pluginEventManager.broadcast(new PluginDisabledEvent(mock(Plugin.class)));
247         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
248         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
249 
250         // Cache has been cleared, now gets loaded again
251         verify(delegate, times(2)).getEnabledPlugins();
252         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
253     }
254 
255     @Test
256     public void testGetEnabledModulesByClassFlushCacheAfterAnyPluginModuleDisable() {
257         Descriptor descriptor = new Descriptor(new Module());
258 
259         // call the cached method - populates cache
260         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
261 
262         // Cache loading of ByModuleClassCacheLoader works via a predicate across all plugins' modules,
263         // not by calling the delegate: see EnabledModuleCachingPluginAccessor.getEnabledModuleDescriptorsByModuleClass()
264         verify(delegate, times(1)).getEnabledPlugins();
265         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
266 
267         pluginEventManager.broadcast(new PluginModuleDisabledEvent(descriptor, false));
268         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
269         cachingPluginAccessor.getEnabledModulesByClass(Module.class);
270 
271         // Cache has been cleared, now gets loaded again
272         verify(delegate, times(2)).getEnabledPlugins();
273         verify(delegate, never()).getEnabledModulesByClass(any(Class.class));
274     }
275 
276     @Test
277     public void testGetEnabledModulesByClassChildAndParentClassBeingTrackedSeparately() {
278         final ChildModule childModule = new ChildModule();
279         final ChildDescriptor<ChildModule> childDescriptor = new ChildDescriptor<>(childModule);
280         childDescriptor.enabled();
281         final ParentModule parentModule = new ParentModule();
282         final ParentDescriptor<ParentModule> parentDescriptor = new ParentDescriptor<>(parentModule);
283         parentDescriptor.enabled();
284 
285         // Mock for cache loader
286         final Plugin plugin = mock(Plugin.class);
287         when(delegate.getEnabledPlugins()).thenReturn(newArrayList(plugin));
288         when(plugin.getModuleDescriptors()).thenReturn(Lists.<ModuleDescriptor<?>>newArrayList(parentDescriptor, childDescriptor));
289 
290         assertThat(cachingPluginAccessor.getEnabledModulesByClass(ChildModule.class),
291                 contains(childModule));
292         assertThat(cachingPluginAccessor.getEnabledModulesByClass(ParentModule.class),
293                 containsInAnyOrder(parentModule, childModule));
294     }
295 
296     @Test
297     public void testGetEnabledModulesByClassWhenModuleEnabledBeforeCacheAccessed() {
298         final Module module = new Module();
299         final Descriptor descriptor = new Descriptor(module);
300         descriptor.enabled();
301 
302         // Mock for cache loader - module already enabled
303         final Plugin plugin = mock(Plugin.class);
304         when(delegate.getEnabledPlugins()).thenReturn(newArrayList(plugin));
305         when(plugin.getModuleDescriptors()).thenReturn(Collections.<ModuleDescriptor<?>>singletonList(descriptor));
306 
307         // Cache loader should see the module
308         List<Module> result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
309         assertThat(result, contains(module));
310     }
311 
312     @Test
313     public void testGetEnabledModulesByClassWhenModuleEnabledAfterCacheAccessed() {
314         final Module module = new Module();
315         final Descriptor descriptor = new Descriptor(module);
316         assertFalse(descriptor.isEnabled());
317 
318         // Mock for cache loader - module returned, but initially disabled
319         final Plugin plugin = mock(Plugin.class);
320         when(delegate.getEnabledPlugins()).thenReturn(newArrayList(plugin));
321         when(plugin.getModuleDescriptors()).thenReturn(Collections.<ModuleDescriptor<?>>singletonList(descriptor));
322 
323         // Cache loader
324         List<Module> result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
325         assertThat(result, empty());
326 
327         // Now gets enabled - tracker should see the event and return the module
328         descriptor.enabled();
329         pluginEventManager.broadcast(new PluginModuleEnabledEvent(descriptor));
330 
331         result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
332         assertThat(result, contains(module));
333     }
334 
335     @Test
336     public void testGetEnabledModulesByClassDoesNotCacheModulesOnlyDescriptors() {
337         // Different module each time 'getModule' is called
338         final Module module1 = mock(Module.class, "module1");
339         final Module module2 = mock(Module.class, "module2");
340         final Module module3 = mock(Module.class, "module3");
341         final MockModuleDescriptor<Module> descriptor = new MockModuleDescriptor<Module>(mock(Plugin.class), "descriptor", module1) {
342             private Iterator<Module> modules = Iterators.forArray(module1, module2, module3);
343 
344             @Override
345             public Module getModule() {
346                 return modules.next();
347             }
348         };
349         descriptor.enabled();
350 
351         // Mock for cache loader - module already enabled
352         final Plugin plugin = mock(Plugin.class);
353         when(delegate.getEnabledPlugins()).thenReturn(newArrayList(plugin));
354         when(plugin.getModuleDescriptors()).thenReturn(Collections.<ModuleDescriptor<?>>singletonList(descriptor));
355 
356         List<Module> result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
357         assertEquals(1, result.size());
358         assertThat(result.get(0), sameInstance(module1));
359 
360         // Module is re-retrieved from descriptor every time
361         result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
362         assertEquals(1, result.size());
363         assertThat(result.get(0), sameInstance(module2));
364 
365         result = cachingPluginAccessor.getEnabledModulesByClass(Module.class);
366         assertEquals(1, result.size());
367         assertThat(result.get(0), sameInstance(module3));
368     }
369 
370     class Module {
371     }
372 
373     class ParentModule {
374     }
375 
376     class ChildModule extends ParentModule {
377     }
378 
379     class Descriptor extends MockModuleDescriptor<Module> {
380         public Descriptor(Module module) {
381             super(mock(Plugin.class), "descriptorKey", module);
382         }
383     }
384 
385     class ParentDescriptor<M extends ParentModule> extends MockModuleDescriptor<M> {
386         public ParentDescriptor(String key, M module) {
387             super(mock(Plugin.class), key, module);
388         }
389 
390         public ParentDescriptor(M module) {
391             this("parentDescriptorKey", module);
392         }
393     }
394 
395     class ChildDescriptor<M extends ChildModule> extends ParentDescriptor<M> {
396         public ChildDescriptor(M module) {
397             super("childDescriptorKey", module);
398         }
399     }
400 }