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")
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
242
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 }