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.ArgumentMatchers.any;
36  import static org.mockito.ArgumentMatchers.anyString;
37  import static org.mockito.ArgumentMatchers.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.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.getPluginState()).thenReturn(PluginState.ENABLED);
141         when(mockPlugin.getModuleDescriptors()).thenReturn(moduleDescriptors);
142         when(mockPlugin.getDependencies()).thenReturn(new PluginDependencies());
143         return mockPlugin;
144     }
145 
146     public static Plugin mockStaticPlugin(final String pluginKey, final ModuleDescriptor<?>... descriptors) {
147         return new StaticPlugin() {
148             {
149                 setPluginInformation(new PluginInformation());
150                 setEnabledByDefault(true);
151                 setKey(pluginKey);
152             }
153 
154             @Override
155             public Collection<ModuleDescriptor<?>> getModuleDescriptors() {
156                 return Arrays.<ModuleDescriptor<?>>asList(descriptors);
157             }
158 
159             @Override
160             public ModuleDescriptor<Object> getModuleDescriptor(final String moduleKey) {
161                 for (ModuleDescriptor desc : descriptors) {
162                     if (desc.getKey().equals(moduleKey))
163                         return desc;
164                 }
165                 return null;
166             }
167         };
168     }
169 
170     /**
171      * Obtain an answer which updates {@link Plugin#getPluginState()} and sends a marked event when called.
172      *
173      * @param plugin             the Plugin to stub.
174      * @param pluginState        the plugin state to report
175      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginStateMarkerEvent} via.
176      * @return an answer which updates {@code plugin.getPluginState()} and fires a {@code PluginStateMarkerEvent}.
177      */
178     public static Plugin doAnswerPluginStateChangeWhen(
179             final Plugin plugin, final PluginState pluginState, final PluginEventManager pluginEventManager) {
180         final Answer answer = new Answer() {
181             @Override
182             public Object answer(final InvocationOnMock invocation) throws Throwable {
183                 when(plugin.getPluginState()).thenReturn(pluginState);
184                 pluginEventManager.broadcast(new PluginStateMarkerEvent(plugin, pluginState));
185                 return null;
186             }
187         };
188         return doAnswer(answer).when(plugin);
189     }
190 
191     public static ModuleDescriptor<?> mockStateChangePluginModule(
192             final String pluginKey, final String moduleKey, final PluginEventManager pluginEventManager) {
193         final ModuleDescriptor<?> module = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
194         when(module.getPluginKey()).thenReturn(pluginKey);
195         when(module.getCompleteKey()).thenReturn(new ModuleCompleteKey(pluginKey, moduleKey).getCompleteKey());
196         doAnswerModuleStateChangeWhen(module, true, pluginEventManager).enabled();
197         doAnswerModuleStateChangeWhen(module, false, pluginEventManager).disabled();
198         return module;
199     }
200 
201     public static Matcher<PluginModuleEvent> pluginModuleStateChange(final ModuleDescriptor module, final boolean enabled) {
202         final Matcher<PluginModuleEvent> pluginModuleEventMatcher = pluginModuleEvent(PluginModuleStateMarkerEvent.class, module);
203         final Matcher<Boolean> pluginModuleStateMatcher = is(enabled);
204 
205         return new TypeSafeMatcher<PluginModuleEvent>() {
206             @Override
207             protected boolean matchesSafely(final PluginModuleEvent event) {
208                 return pluginModuleEventMatcher.matches(event)
209                         // pluginModuleEventMatcher matches, so event instanceof PluginModuleStateMarkerEvent, so this cast is safe
210                         && pluginModuleStateMatcher.matches(((PluginModuleStateMarkerEvent) event).isEnabled());
211             }
212 
213             @Override
214             public void describeTo(final Description description) {
215                 pluginModuleEventMatcher.describeTo(description);
216                 description.appendText(" and .isEnabled() ");
217                 pluginModuleStateMatcher.describeTo(description);
218             }
219         };
220     }
221 
222     /**
223      * Obtain an answer which updates {@link ModuleDescriptor#isEnabled} and sends a marked event when called.
224      *
225      * @param module             the moduleDescriptor to stub, which must also extend {@link StateAware}.
226      * @param enabled            the enabled state to report
227      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginModuleStateMarkerEvent} via.
228      * @return an answer which stubs {@code moduleDescriptor.isEnabled} and fires a {@code PluginModuleStateMarkerEvent}.
229      */
230     public static StateAware doAnswerModuleStateChangeWhen(
231             final ModuleDescriptor<?> module, final boolean enabled, final PluginEventManager pluginEventManager) {
232         final Answer answer = new Answer() {
233             @Override
234             public Object answer(final InvocationOnMock invocation) throws Throwable {
235                 when(module.isEnabled()).thenReturn(enabled);
236                 pluginEventManager.broadcast(new PluginModuleStateMarkerEvent(module, enabled));
237                 return null;
238             }
239         };
240         return doAnswer(answer).when((StateAware) module);
241     }
242 
243     /**
244      * This is a trick to conveniently check sequencing of plugin module enabled change functions wrt other events.
245      */
246     public static class PluginModuleStateMarkerEvent extends PluginModuleEvent {
247         private final boolean enabled;
248 
249         PluginModuleStateMarkerEvent(final ModuleDescriptor module, final boolean enabled) {
250             super(module);
251             this.enabled = enabled;
252         }
253 
254         public boolean isEnabled() {
255             return enabled;
256         }
257 
258         @Override
259         public String toString() {
260             return super.toString() + " with enabled " + Boolean.toString(enabled);
261         }
262     }
263 
264     public static Matcher<PluginModuleEvent> pluginModuleEvent(final Class clazz, final ModuleDescriptor module) {
265         final Matcher<Class> classMatcher = instanceOf(clazz);
266         final Matcher<ModuleDescriptor> pluginModuleMatcher = is(module);
267         return new TypeSafeMatcher<PluginModuleEvent>() {
268             @Override
269             protected boolean matchesSafely(final PluginModuleEvent pluginModuleEvent) {
270                 return classMatcher.matches(pluginModuleEvent) && pluginModuleMatcher.matches(pluginModuleEvent.getModule());
271             }
272 
273             @Override
274             public void describeTo(final Description description) {
275                 classMatcher.describeTo(description);
276                 description.appendText(" for which .getModule() ");
277                 pluginModuleMatcher.describeTo(description);
278             }
279         };
280     }
281 
282     /**
283      * This is a trick to conveniently check sequencing of plugin state change functions wrt other events.
284      */
285     public static class PluginStateMarkerEvent extends PluginEvent {
286         private final PluginState pluginState;
287 
288         PluginStateMarkerEvent(final Plugin plugin, final PluginState pluginState) {
289             super(plugin);
290             this.pluginState = pluginState;
291         }
292 
293         public PluginState getPluginState() {
294             return pluginState;
295         }
296 
297         @Override
298         public String toString() {
299             return super.toString() + " with pluginState " + pluginState;
300         }
301     }
302 
303     public static Matcher<PluginEvent> anyPluginStateChange(final PluginState pluginState, final Plugin... plugin) {
304         final Matcher<PluginEvent> pluginEventMatcher = anyPluginEvent(PluginStateMarkerEvent.class, plugin);
305         final Matcher<PluginState> pluginStateMatcher = is(pluginState);
306 
307         return new TypeSafeMatcher<PluginEvent>() {
308             @Override
309             protected boolean matchesSafely(final PluginEvent event) {
310                 return pluginEventMatcher.matches(event)
311                         // pluginEventMatcher matches, so event instanceof PluginStateMarkerEvent, so this cast is safe
312                         && pluginStateMatcher.matches(((PluginStateMarkerEvent) event).getPluginState());
313             }
314 
315             @Override
316             public void describeTo(final Description description) {
317                 pluginEventMatcher.describeTo(description);
318                 description.appendText(" and .getPluginState() ");
319                 pluginStateMatcher.describeTo(description);
320             }
321         };
322     }
323 
324     public static Matcher<PluginEvent> pluginStateChange(final Plugin plugin, final PluginState pluginState) {
325         return anyPluginStateChange(pluginState, plugin);
326     }
327 
328     public static Matcher<PluginEvent> anyPluginEvent(final Class clazz, final Plugin... plugins) {
329         final Matcher<Class> classMatcher = instanceOf(clazz);
330         final Matcher<Plugin> pluginMatcher = anyOf(Arrays.stream(plugins).map(Matchers::is).collect(Collectors.toList()));
331 
332         return new TypeSafeMatcher<PluginEvent>() {
333             @Override
334             protected boolean matchesSafely(final PluginEvent pluginEvent) {
335                 return classMatcher.matches(pluginEvent) && pluginMatcher.matches(pluginEvent.getPlugin());
336             }
337 
338             @Override
339             public void describeTo(final Description description) {
340                 classMatcher.describeTo(description);
341                 description.appendText(" for which .getPlugin() ");
342                 pluginMatcher.describeTo(description);
343             }
344         };
345     }
346 
347     public static Matcher<PluginEvent> pluginEvent(final Class clazz, final Plugin plugin) {
348         return anyPluginEvent(clazz, plugin);
349     }
350 }