View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.ModuleDescriptorFactory;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginRestartState;
6   import com.atlassian.plugin.PluginState;
7   import com.atlassian.plugin.event.PluginEventManager;
8   import com.atlassian.plugin.loaders.PluginLoader;
9   import com.google.common.collect.ImmutableList;
10  import com.google.common.collect.Iterables;
11  import org.junit.Before;
12  import org.junit.Rule;
13  import org.junit.Test;
14  import org.mockito.Mock;
15  import org.mockito.junit.MockitoJUnit;
16  import org.mockito.junit.MockitoRule;
17  import org.mockito.stubbing.Answer;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.Set;
22  import java.util.stream.Collectors;
23  
24  import static com.atlassian.plugin.PluginState.DISABLED;
25  import static com.atlassian.plugin.PluginState.ENABLED;
26  import static com.atlassian.plugin.PluginState.UNINSTALLED;
27  import static org.hamcrest.MatcherAssert.assertThat;
28  import static org.hamcrest.Matchers.containsInAnyOrder;
29  import static org.hamcrest.Matchers.is;
30  import static org.hamcrest.Matchers.nullValue;
31  import static org.junit.Assert.fail;
32  import static org.mockito.ArgumentMatchers.any;
33  import static org.mockito.Mockito.doAnswer;
34  import static org.mockito.Mockito.when;
35  
36  public class TestDefaultPluginManagerCycled extends TestDependentPlugins {
37  
38      private static final List<Set<SingleAction>> initialEnabling = ImmutableList.of(
39              actions(ENABLED,
40                      "A", "a", "B", "b", "aC", "c", "C",
41                      "D", "E", "F", "G", "H",
42                      "k5", "k4", "k3", "k2", "k1", "k0",
43                      "mX", "mA", "mB", "mC", "mD", "mE") // plugins are enabled in unspecified order
44      );
45  
46      @Rule
47      public final MockitoRule mockito = MockitoJUnit.rule();
48  
49      @Mock
50      private PluginPersistentStateStore pluginPersistentStateStore;
51      @Mock
52      private ModuleDescriptorFactory moduleDescriptorFactory;
53      @Mock
54      private PluginEventManager pluginEventManager;
55      @Mock
56      private PluginPersistentState pluginPersistentState;
57      @Mock
58      private PluginLoader pluginLoader;
59  
60      private DefaultPluginManager pluginManager;
61  
62      @Before
63      public void setUp() throws Exception {
64          super.setUp();
65  
66          when(pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(allPlugins);
67          when(pluginLoader.supportsRemoval()).thenReturn(true);
68  
69          doAnswer((Answer<Void>) invocationOnMock -> {
70              invocationOnMock.<Plugin>getArgument(0).uninstall();
71              return null;
72          }).when(pluginLoader).removePlugin(any(Plugin.class));
73  
74          pluginManager = new DefaultPluginManager(
75                  pluginPersistentStateStore,
76                  Arrays.asList(pluginLoader),
77                  moduleDescriptorFactory,
78                  pluginEventManager);
79          when(pluginPersistentStateStore.load()).thenReturn(pluginPersistentState);
80          when(pluginPersistentState.getPluginRestartState(any(String.class))).thenReturn(PluginRestartState.NONE);
81          pluginManager.init();
82  
83          pluginManager.enablePlugins(getKeys(allPlugins).toArray(new String[allPlugins.size()]));
84      }
85  
86      @Test
87      public void disableSinglePlugin() {
88          test(PluginState.DISABLED,
89                  ImmutableList.of("G"),
90                  ImmutableList.<Set<SingleAction>>builder()
91                          .add(actions(DISABLED, "A", "F", "H"))
92                          .add(actions(DISABLED, "D", "E"))
93                          .add(actions(DISABLED, "C"))
94                          .add(actions(DISABLED, "G"))
95                          .add(actions(ENABLED, "D"))
96                          .add(actions(ENABLED, "A", "F"))
97                          .build());
98      }
99  
100     @Test
101     public void uninstallSinglePlugin() {
102         test(PluginState.UNINSTALLED,
103                 ImmutableList.of("G"),
104                 ImmutableList.<Set<SingleAction>>builder()
105                         .add(actions(DISABLED, "A", "F", "H"))
106                         .add(actions(DISABLED, "D", "E", "B"))
107                         .add(actions(DISABLED, "C"))
108                         .add(actions(DISABLED, "G"))
109                         .add(actions(UNINSTALLED, "G"))
110                         .add(actions(ENABLED, "D", "B"))
111                         .add(actions(ENABLED, "A", "F"))
112                         .build());
113     }
114 
115     @Test
116     public void disableSinglePluginWithCircularDependency() {
117         test(PluginState.DISABLED,
118                 ImmutableList.of("a"),
119                 ImmutableList.<Set<SingleAction>>builder()
120                         .add(actions(DISABLED, "b"))
121                         .add(actions(DISABLED, "c"))
122                         .add(actions(DISABLED, "a"))
123                         .build());
124     }
125 
126     @Test
127     public void uninstallSinglePluginWithCircularDependency() {
128         test(PluginState.UNINSTALLED,
129                 ImmutableList.of("a"),
130                 ImmutableList.<Set<SingleAction>>builder()
131                         .add(actions(DISABLED, "b"))
132                         .add(actions(DISABLED, "c"))
133                         .add(actions(DISABLED, "a"))
134                         .add(actions(UNINSTALLED, "a"))
135                         .build());
136     }
137 
138     @Test
139     public void uninstallMultiplePlugins() {
140         test(PluginState.UNINSTALLED,
141                 ImmutableList.of("G", "D", "E"),
142                 ImmutableList.<Set<SingleAction>>builder()
143                         .add(actions(DISABLED, "A", "F", "H"))
144                         .add(actions(DISABLED, "D", "E", "B", "C"))
145                         .add(actions(DISABLED, "G"))
146                         .add(actions(UNINSTALLED, "G", "D", "E"))
147                         .add(actions(ENABLED, "B"))
148                         .add(actions(ENABLED, "A"))
149                         .build());
150     }
151 
152     @Test
153     public void uninstallMultiplePluginsWithCircularDependency() {
154         test(PluginState.UNINSTALLED,
155                 ImmutableList.of("b", "c"),
156                 ImmutableList.<Set<SingleAction>>builder()
157                         .add(actions(DISABLED, "b", "c", "a"))
158                         .add(actions(UNINSTALLED, "b", "c"))
159                         .build());
160     }
161 
162     @Test
163     public void uninstallMultiplePluginsWithMultipleDependencyTypes() {
164         test(PluginState.UNINSTALLED,
165                 ImmutableList.of("mC", "mD"),
166                 ImmutableList.<Set<SingleAction>>builder()
167                         .add(actions(DISABLED, "mX"))
168                         .add(actions(DISABLED, "mA"))
169                         .add(actions(DISABLED, "mB", "mD", "mE", "mC"))
170                         .add(actions(UNINSTALLED, "mC", "mD"))
171                         .add(actions(ENABLED, "mB"))
172                         .build());
173     }
174 
175     @Test
176     public void uninstallLongChain() {
177         test(PluginState.UNINSTALLED,
178                 ImmutableList.of("k0"),
179                 ImmutableList.<Set<SingleAction>>builder()
180                         .add(actions(DISABLED, "k5"))
181                         .add(actions(DISABLED, "k4"))
182                         .add(actions(DISABLED, "k3"))
183                         .add(actions(DISABLED, "k2"))
184                         .add(actions(DISABLED, "k1"))
185                         .add(actions(DISABLED, "k0"))
186                         .add(actions(UNINSTALLED, "k0"))
187                         .build());
188     }
189 
190     private void test(PluginState action, List<String> target, List<Set<SingleAction>> expectedActions) {
191         performActionOnPlugins(action, target);
192         assertOperations(expectedActions);
193         assertTargetPluginStates(action, target);
194     }
195 
196     private void performActionOnPlugins(PluginState action, List<String> target) {
197         switch (action) {
198             case DISABLED:
199                 pluginManager.disablePlugin(target.get(0));
200                 break;
201             case UNINSTALLED:
202                 if (target.size()==1) {
203                     pluginManager.uninstall(pluginManager.getPlugin(target.get(0)));
204                 } else {
205                     pluginManager.uninstallPlugins(target.stream().map(pluginManager::getPlugin).collect(Collectors.toList()));
206                 }
207                 break;
208             default:
209                 fail("Unsupported action " + action);
210         }
211     }
212 
213     private void assertOperations(List<Set<SingleAction>> expectedActions) {
214         int resultsIdx = 0;
215         int resultIdxTo;
216         for (final Set<SingleAction> result : Iterables.concat(initialEnabling, expectedActions)) {
217             resultIdxTo = resultsIdx + result.size();
218             final List<SingleAction> singleActions = pluginActions.subList(resultsIdx, resultIdxTo);
219             assertThat("[" + resultsIdx + ", " + resultIdxTo + "]",
220                     singleActions,
221                     containsInAnyOrder(result.toArray()));
222             resultsIdx = resultIdxTo;
223         }
224         assertThat("Unexpected events count", pluginActions.size(), is(resultsIdx));
225     }
226 
227     private void assertTargetPluginStates(PluginState action, List<String> target) {
228         switch (action) {
229             case DISABLED:
230                 target.forEach(key -> assertThat(pluginManager.getPlugin(key).getPluginState(), is(PluginState.DISABLED)));
231                 break;
232             case UNINSTALLED:
233                 target.forEach(key -> assertThat(pluginManager.getPlugin(key), is(nullValue())));
234                 break;
235             default:
236                 fail("Unsupported action " + action);
237         }
238     }
239 
240     /**
241      * A set of actions that happen to plugins in unspecified order,
242      * e.g. disabling 3 plugins, neither of which has any dependencies.
243      */
244     private static Set<SingleAction> actions(PluginState action, String... pluginKeys) {
245         return Arrays.stream(pluginKeys)
246                 .map(k -> new SingleAction(k, action))
247                 .collect(Collectors.toSet());
248     }
249 }