View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.ModuleCompleteKey;
4   import com.atlassian.plugin.ModuleDescriptor;
5   import com.atlassian.plugin.ModuleDescriptorFactory;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginDependencies;
8   import com.atlassian.plugin.PluginInformation;
9   import com.atlassian.plugin.PluginRestartState;
10  import com.atlassian.plugin.PluginState;
11  import com.atlassian.plugin.StateAware;
12  import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
13  import com.atlassian.plugin.event.PluginEventManager;
14  import com.atlassian.plugin.event.events.PluginEvent;
15  import com.atlassian.plugin.event.events.PluginModuleEvent;
16  import com.atlassian.plugin.impl.StaticPlugin;
17  import com.atlassian.plugin.loaders.PluginLoader;
18  import com.atlassian.plugin.module.ModuleFactory;
19  import com.google.common.collect.ImmutableList;
20  import com.google.common.collect.Lists;
21  import org.hamcrest.Description;
22  import org.hamcrest.Matcher;
23  import org.hamcrest.Matchers;
24  import org.hamcrest.TypeSafeMatcher;
25  import org.mockito.invocation.InvocationOnMock;
26  import org.mockito.stubbing.Answer;
27  
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.stream.Collectors;
31  
32  import static org.hamcrest.Matchers.instanceOf;
33  import static org.hamcrest.Matchers.is;
34  import static org.hamcrest.core.AnyOf.anyOf;
35  import static org.mockito.Matchers.any;
36  import static org.mockito.Matchers.anyString;
37  import static org.mockito.Matchers.isA;
38  import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
39  import static org.mockito.Mockito.doAnswer;
40  import static org.mockito.Mockito.mock;
41  import static org.mockito.Mockito.when;
42  import static org.mockito.Mockito.withSettings;
43  
44  class DefaultPluginManagerMocks {
45      public enum FailureMode {
46          FAIL_TO_ENABLE,
47          FAIL_TO_DISABLE
48      }
49  
50      public static ModuleDescriptor<Object> mockFailingModuleDescriptor(final String completeKey, final FailureMode... failureModes) {
51          return new AbstractModuleDescriptor<Object>(ModuleFactory.LEGACY_MODULE_FACTORY) {
52              @Override
53              public String getKey() {
54                  return completeKey.substring(completeKey.lastIndexOf(":") + 1, completeKey.length());
55              }
56  
57              @Override
58              public String getCompleteKey() {
59                  return completeKey;
60              }
61  
62              @Override
63              public void enabled() {
64                  if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_ENABLE))
65                      throw new IllegalArgumentException("Cannot enable");
66              }
67  
68              @Override
69              public void disabled() {
70                  if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_DISABLE))
71                      throw new IllegalArgumentException("Cannot disable");
72              }
73  
74              @Override
75              public Object getModule() {
76                  return null;
77              }
78          };
79      }
80  
81      public static PluginLoader mockPluginLoaderForPlugins(final Plugin... plugins) {
82          final PluginLoader pluginLoader = mock(PluginLoader.class);
83          when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugins));
84          when(pluginLoader.supportsRemoval()).thenReturn(true);
85          return pluginLoader;
86      }
87  
88      public static void mockPluginsSortOrder(final Plugin... mockPlugins) {
89          for (int i = 0; i < mockPlugins.length; ++i) {
90              for (int j = 0; j < mockPlugins.length; ++j) {
91                  // Integer.compareTo is JDK 1.7+ only, so we just implement compare directly.
92                  when(mockPlugins[i].compareTo(mockPlugins[j])).thenReturn(i - j);
93              }
94          }
95      }
96  
97      public static PluginPersistentStateStore mockPluginPersistentStateStore() {
98          final PluginPersistentStateStore pluginPersistentStateStore = mock(PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
99          when(pluginPersistentStateStore.load().getPluginRestartState(anyString())).thenReturn(PluginRestartState.NONE);
100         when(pluginPersistentStateStore.load().isEnabled(any(Plugin.class))).thenReturn(true);
101         when(pluginPersistentStateStore.load().isEnabled(any(ModuleDescriptor.class))).thenReturn(true);
102         return pluginPersistentStateStore;
103     }
104 
105     public static Plugin mockStateChangePlugin(final String pluginKey, final PluginEventManager pluginEventManager) {
106         final Plugin plugin = mockPlugin(pluginKey);
107 
108         doAnswerPluginStateChangeWhen(plugin, PluginState.INSTALLED, pluginEventManager).install();
109         doAnswerPluginStateChangeWhen(plugin, PluginState.ENABLED, pluginEventManager).enable();
110         doAnswerPluginStateChangeWhen(plugin, PluginState.DISABLED, pluginEventManager).disable();
111         return plugin;
112     }
113 
114     public static Plugin mockPluginWithVersion(final String pluginKey, final String version) {
115         final Plugin plugin = mockPlugin(pluginKey);
116         when(plugin.getPluginInformation().getVersion()).thenReturn(version);
117         return plugin;
118     }
119 
120     public static Plugin mockPlugin(final String pluginKey) {
121         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
122         when(plugin.getKey()).thenReturn(pluginKey);
123         when(plugin.getPluginState()).thenReturn(PluginState.UNINSTALLED);
124         when(plugin.getModuleDescriptors()).thenReturn(ImmutableList.<ModuleDescriptor<?>>of());
125         when(plugin.getPluginInformation().satisfiesMinJavaVersion()).thenReturn(true);
126         when(plugin.isUninstallable()).thenReturn(true);
127         when(plugin.toString()).thenAnswer(new Answer<String>() {
128             @Override
129             public String answer(InvocationOnMock invocation) throws Throwable {
130                 return plugin.getKey() + ":" + plugin.getPluginInformation().getVersion();
131             }
132         });
133         return plugin;
134     }
135 
136     public static Plugin mockTestPlugin(Collection moduleDescriptors) {
137         final Plugin mockPlugin = mock(Plugin.class);
138         when(mockPlugin.getKey()).thenReturn("some-plugin-key");
139         when(mockPlugin.isEnabledByDefault()).thenReturn(true);
140         when(mockPlugin.isEnabled()).thenReturn(true);
141         when(mockPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
142         when(mockPlugin.getModuleDescriptors()).thenReturn(moduleDescriptors);
143         when(mockPlugin.getDependencies()).thenReturn(new PluginDependencies());
144         return mockPlugin;
145     }
146 
147     public static Plugin mockStaticPlugin(final String pluginKey, final ModuleDescriptor<?>... descriptors) {
148         return new StaticPlugin() {
149             {
150                 setPluginInformation(new PluginInformation());
151                 setEnabledByDefault(true);
152                 setKey(pluginKey);
153             }
154 
155             @Override
156             public Collection<ModuleDescriptor<?>> getModuleDescriptors() {
157                 return Arrays.<ModuleDescriptor<?>>asList(descriptors);
158             }
159 
160             @Override
161             public ModuleDescriptor<Object> getModuleDescriptor(final String moduleKey) {
162                 for (ModuleDescriptor desc : descriptors) {
163                     if (desc.getKey().equals(moduleKey))
164                         return desc;
165                 }
166                 return null;
167             }
168         };
169     }
170 
171     /**
172      * Obtain an answer which updates {@link Plugin#getPluginState()} and sends a marked event when called.
173      *
174      * @param plugin             the Plugin to stub.
175      * @param pluginState        the plugin state to report
176      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginStateMarkerEvent} via.
177      * @return an answer which updates {@code plugin.getPluginState()} and fires a {@code PluginStateMarkerEvent}.
178      */
179     public static Plugin doAnswerPluginStateChangeWhen(
180             final Plugin plugin, final PluginState pluginState, final PluginEventManager pluginEventManager) {
181         final Answer answer = new Answer() {
182             @Override
183             public Object answer(final InvocationOnMock invocation) throws Throwable {
184                 when(plugin.getPluginState()).thenReturn(pluginState);
185                 pluginEventManager.broadcast(new PluginStateMarkerEvent(plugin, pluginState));
186                 return null;
187             }
188         };
189         return doAnswer(answer).when(plugin);
190     }
191 
192     public static ModuleDescriptor<?> mockStateChangePluginModule(
193             final String pluginKey, final String moduleKey, final PluginEventManager pluginEventManager) {
194         final ModuleDescriptor<?> module = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
195         when(module.getPluginKey()).thenReturn(pluginKey);
196         when(module.getCompleteKey()).thenReturn(new ModuleCompleteKey(pluginKey, moduleKey).getCompleteKey());
197         doAnswerModuleStateChangeWhen(module, true, pluginEventManager).enabled();
198         doAnswerModuleStateChangeWhen(module, false, pluginEventManager).disabled();
199         return module;
200     }
201 
202     public static Matcher<PluginModuleEvent> pluginModuleStateChange(final ModuleDescriptor module, final boolean enabled) {
203         final Matcher<PluginModuleEvent> pluginModuleEventMatcher = pluginModuleEvent(PluginModuleStateMarkerEvent.class, module);
204         final Matcher<Boolean> pluginModuleStateMatcher = is(enabled);
205 
206         return new TypeSafeMatcher<PluginModuleEvent>() {
207             @Override
208             protected boolean matchesSafely(final PluginModuleEvent event) {
209                 return pluginModuleEventMatcher.matches(event)
210                         // pluginModuleEventMatcher matches, so event instanceof PluginModuleStateMarkerEvent, so this cast is safe
211                         && pluginModuleStateMatcher.matches(((PluginModuleStateMarkerEvent) event).isEnabled());
212             }
213 
214             @Override
215             public void describeTo(final Description description) {
216                 pluginModuleEventMatcher.describeTo(description);
217                 description.appendText(" and .isEnabled() ");
218                 pluginModuleStateMatcher.describeTo(description);
219             }
220         };
221     }
222 
223     /**
224      * Obtain an answer which updates {@link ModuleDescriptor#isEnabled} and sends a marked event when called.
225      *
226      * @param module             the moduleDescriptor to stub, which must also extend {@link StateAware}.
227      * @param enabled            the enabled state to report
228      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginModuleStateMarkerEvent} via.
229      * @return an answer which stubs {@code moduleDescriptor.isEnabled} and fires a {@code PluginModuleStateMarkerEvent}.
230      */
231     public static StateAware doAnswerModuleStateChangeWhen(
232             final ModuleDescriptor<?> module, final boolean enabled, final PluginEventManager pluginEventManager) {
233         final Answer answer = new Answer() {
234             @Override
235             public Object answer(final InvocationOnMock invocation) throws Throwable {
236                 when(module.isEnabled()).thenReturn(enabled);
237                 pluginEventManager.broadcast(new PluginModuleStateMarkerEvent(module, enabled));
238                 return null;
239             }
240         };
241         return doAnswer(answer).when((StateAware) module);
242     }
243 
244     /**
245      * This is a trick to conveniently check sequencing of plugin module enabled change functions wrt other events.
246      */
247     public static class PluginModuleStateMarkerEvent extends PluginModuleEvent {
248         private final boolean enabled;
249 
250         PluginModuleStateMarkerEvent(final ModuleDescriptor module, final boolean enabled) {
251             super(module);
252             this.enabled = enabled;
253         }
254 
255         public boolean isEnabled() {
256             return enabled;
257         }
258 
259         @Override
260         public String toString() {
261             return super.toString() + " with enabled " + Boolean.toString(enabled);
262         }
263     }
264 
265     public static Matcher<PluginModuleEvent> pluginModuleEvent(final Class clazz, final ModuleDescriptor module) {
266         final Matcher<Class> classMatcher = instanceOf(clazz);
267         final Matcher<ModuleDescriptor> pluginModuleMatcher = is(module);
268         return new TypeSafeMatcher<PluginModuleEvent>() {
269             @Override
270             protected boolean matchesSafely(final PluginModuleEvent pluginModuleEvent) {
271                 return classMatcher.matches(pluginModuleEvent) && pluginModuleMatcher.matches(pluginModuleEvent.getModule());
272             }
273 
274             @Override
275             public void describeTo(final Description description) {
276                 classMatcher.describeTo(description);
277                 description.appendText(" for which .getModule() ");
278                 pluginModuleMatcher.describeTo(description);
279             }
280         };
281     }
282 
283     /**
284      * This is a trick to conveniently check sequencing of plugin state change functions wrt other events.
285      */
286     public static class PluginStateMarkerEvent extends PluginEvent {
287         private final PluginState pluginState;
288 
289         PluginStateMarkerEvent(final Plugin plugin, final PluginState pluginState) {
290             super(plugin);
291             this.pluginState = pluginState;
292         }
293 
294         public PluginState getPluginState() {
295             return pluginState;
296         }
297 
298         @Override
299         public String toString() {
300             return super.toString() + " with pluginState " + pluginState;
301         }
302     }
303 
304     public static Matcher<PluginEvent> anyPluginStateChange(final PluginState pluginState, final Plugin... plugin) {
305         final Matcher<PluginEvent> pluginEventMatcher = anyPluginEvent(PluginStateMarkerEvent.class, plugin);
306         final Matcher<PluginState> pluginStateMatcher = is(pluginState);
307 
308         return new TypeSafeMatcher<PluginEvent>() {
309             @Override
310             protected boolean matchesSafely(final PluginEvent event) {
311                 return pluginEventMatcher.matches(event)
312                         // pluginEventMatcher matches, so event instanceof PluginStateMarkerEvent, so this cast is safe
313                         && pluginStateMatcher.matches(((PluginStateMarkerEvent) event).getPluginState());
314             }
315 
316             @Override
317             public void describeTo(final Description description) {
318                 pluginEventMatcher.describeTo(description);
319                 description.appendText(" and .getPluginState() ");
320                 pluginStateMatcher.describeTo(description);
321             }
322         };
323     }
324 
325     public static Matcher<PluginEvent> pluginStateChange(final Plugin plugin, final PluginState pluginState) {
326         return anyPluginStateChange(pluginState, plugin);
327     }
328 
329     public static Matcher<PluginEvent> anyPluginEvent(final Class clazz, final Plugin... plugins) {
330         final Matcher<Class> classMatcher = instanceOf(clazz);
331         final Matcher<Plugin> pluginMatcher = anyOf(Arrays.stream(plugins).map(Matchers::is).collect(Collectors.toList()));
332 
333         return new TypeSafeMatcher<PluginEvent>() {
334             @Override
335             protected boolean matchesSafely(final PluginEvent pluginEvent) {
336                 return classMatcher.matches(pluginEvent) && pluginMatcher.matches(pluginEvent.getPlugin());
337             }
338 
339             @Override
340             public void describeTo(final Description description) {
341                 classMatcher.describeTo(description);
342                 description.appendText(" for which .getPlugin() ");
343                 pluginMatcher.describeTo(description);
344             }
345         };
346     }
347 
348     public static Matcher<PluginEvent> pluginEvent(final Class clazz, final Plugin plugin) {
349         return anyPluginEvent(clazz, plugin);
350     }
351 }