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