1 package com.atlassian.plugin.osgi.factory;
2
3 import com.atlassian.plugin.PluginState;
4 import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
5 import com.atlassian.plugin.descriptors.RequiresRestart;
6 import com.atlassian.plugin.event.PluginEventManager;
7 import com.atlassian.plugin.event.events.PluginContainerFailedEvent;
8 import com.atlassian.plugin.event.events.PluginContainerRefreshedEvent;
9 import com.atlassian.plugin.module.ModuleFactory;
10 import com.atlassian.plugin.osgi.container.OsgiContainerException;
11 import com.atlassian.plugin.osgi.util.OsgiSystemBundleUtil;
12 import org.junit.After;
13 import org.junit.Before;
14 import org.junit.Test;
15 import org.junit.runner.RunWith;
16 import org.mockito.Mock;
17 import org.mockito.invocation.InvocationOnMock;
18 import org.mockito.junit.MockitoJUnitRunner;
19 import org.mockito.stubbing.Answer;
20 import org.osgi.framework.Bundle;
21 import org.osgi.framework.BundleContext;
22 import org.osgi.framework.BundleException;
23 import org.osgi.framework.Constants;
24 import org.osgi.service.packageadmin.PackageAdmin;
25
26 import java.util.Dictionary;
27 import java.util.Hashtable;
28
29 import static org.hamcrest.Matchers.arrayContaining;
30 import static org.hamcrest.core.Is.is;
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertThat;
33 import static org.junit.Assert.assertTrue;
34 import static org.mockito.Mockito.doAnswer;
35 import static org.mockito.Mockito.doThrow;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.never;
38 import static org.mockito.Mockito.times;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
42
43 @RunWith(MockitoJUnitRunner.Silent.class)
44 public class TestOsgiPlugin {
45 private static final String PLUGIN_KEY = "plugin-key";
46
47 @Mock
48 private Bundle bundle;
49 @Mock
50 private BundleContext bundleContext;
51 @Mock
52 private Bundle systemBundle;
53 @Mock
54 private OsgiPluginHelper helper;
55 @Mock
56 private PluginEventManager pluginEventManager;
57 @Mock
58 private PackageAdmin packageAdmin;
59
60 private OsgiPlugin plugin;
61
62 @Before
63 public void setUp() {
64 final Dictionary<String, String> bundleHeaders = new Hashtable<>();
65 bundleHeaders.put(Constants.BUNDLE_DESCRIPTION, "desc");
66 bundleHeaders.put(Constants.BUNDLE_VERSION, "1.0");
67 when(bundle.getHeaders()).thenReturn(bundleHeaders);
68 when(bundle.getBundleContext()).thenReturn(bundleContext);
69
70 when(bundleContext.getBundle(OsgiSystemBundleUtil.SYSTEM_BUNDLE_ID)).thenReturn(systemBundle);
71
72 final BundleContext systemBundleContext = mock(BundleContext.class);
73 when(systemBundle.getBundleContext()).thenReturn(systemBundleContext);
74
75 when(helper.getBundle()).thenReturn(bundle);
76
77 plugin = new OsgiPlugin(PLUGIN_KEY, pluginEventManager, helper, packageAdmin);
78 }
79
80 @After
81 public void tearDown() {
82 bundle = null;
83 plugin = null;
84 bundleContext = null;
85 }
86
87 @Test
88 public void testEnabled() throws BundleException {
89 _enablePlugin();
90 verify(bundle).start();
91 }
92
93 private void _enablePlugin() {
94 when(bundle.getState()).thenReturn(Bundle.RESOLVED);
95 plugin.enable();
96 }
97
98 @Test
99 public void testDisabled() throws BundleException {
100 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
101 plugin.disable();
102 verify(bundle).stop();
103 }
104
105 @Test
106 public void testDisabledOnNonDynamicPlugin() throws BundleException {
107 plugin.addModuleDescriptor(new StaticModuleDescriptor());
108 plugin.onPluginFrameworkStartedEvent(null);
109 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
110 plugin.disable();
111 verify(bundle, never()).stop();
112 }
113
114 @Test
115 public void testUninstall() throws BundleException {
116 _enablePlugin();
117 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
118 _assertPluginUninstalled();
119 }
120
121 @Test
122 public void testUninstallingUninstalledBundle() throws BundleException {
123 _enablePlugin();
124 when(bundle.getState()).thenReturn(Bundle.ACTIVE).thenReturn(Bundle.UNINSTALLED);
125 _assertPluginUninstalled();
126 _assertPluginUninstalled();
127 }
128
129 private void _assertPluginUninstalled() {
130 plugin.uninstallInternal();
131 assertEquals(plugin.getPluginState(), PluginState.UNINSTALLED);
132 }
133
134 @Test
135 public void testOnPluginContainerRefresh() {
136 _enablePlugin();
137 assertEquals(PluginState.ENABLING, plugin.getPluginState());
138 final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(new Object(), PLUGIN_KEY);
139 plugin.onPluginContainerRefresh(event);
140 assertEquals(PluginState.ENABLED, plugin.getPluginState());
141 }
142
143 @Test
144 public void testQuickOnPluginContainerRefresh() throws BundleException, InterruptedException {
145 when(bundle.getState()).thenReturn(Bundle.RESOLVED);
146
147 final ConcurrentStateEngine states = new ConcurrentStateEngine(
148 "bundle-starting", "container-created", "bundle-started", "mid-start", "end");
149 when(bundle.getBundleContext()).thenAnswer(new Answer<Object>() {
150 public Object answer(final InvocationOnMock invocation) throws Throwable {
151 states.tryNextState("bundle-started", "mid-start");
152 final BundleContext context = mock(BundleContext.class);
153 when(context.getBundle(OsgiSystemBundleUtil.SYSTEM_BUNDLE_ID)).thenReturn(systemBundle);
154
155 return context;
156 }
157 });
158
159 doAnswer(new Answer<Object>() {
160 public Object answer(final InvocationOnMock invocation) throws Throwable {
161 states.state("bundle-starting");
162 final Thread t = new Thread() {
163 public void run() {
164 final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(
165 new Object(), PLUGIN_KEY);
166 states.tryNextState("bundle-starting", "container-created");
167 plugin.onPluginContainerRefresh(event);
168 }
169 };
170 t.start();
171 states.tryNextState("container-created", "bundle-started");
172 return null;
173 }
174 }).when(bundle).start();
175
176 plugin.enable();
177
178
179 states.tryNextState("mid-start", "end");
180
181 assertEquals(PluginState.ENABLED, plugin.getPluginState());
182 }
183
184 @Test
185 public void testOnPluginContainerRefreshNotEnabling() {
186 final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(new Object(), PLUGIN_KEY);
187 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
188 plugin.disable();
189 plugin.onPluginContainerRefresh(event);
190 assertEquals(PluginState.DISABLED, plugin.getPluginState());
191 }
192
193 @Test
194 public void testUninstallRetryLogic() throws BundleException {
195 try {
196 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
197 plugin.enable();
198 when(bundle.getSymbolicName()).thenReturn("Mock Bundle");
199 doThrow(new BundleException("Mock Bundle Exception")).when(bundle).uninstall();
200 plugin.uninstallInternal();
201 } catch (final Exception e) {
202 assertTrue("Should throw an OsgiContainerException", e instanceof OsgiContainerException);
203 } finally {
204 assertEquals(PluginState.ENABLED, plugin.getPluginState());
205 verify(bundle, times(3)).uninstall();
206 }
207 }
208
209 @Test
210 public void recoverFromPartialUninstall() throws Exception {
211
212
213
214
215 _enablePlugin();
216 when(bundle.getState()).thenReturn(Bundle.UNINSTALLED);
217 _assertPluginUninstalled();
218 verify(bundle, never()).uninstall();
219 verify(helper).onUninstall();
220 verify(pluginEventManager).unregister(plugin);
221 }
222
223 @Test
224 public void resolveInvokesBundleResolution() {
225 plugin.resolve();
226 verify(packageAdmin).resolveBundles(argThat(arrayContaining(bundle)));
227 }
228
229 @Test
230 public void disableOnSpringTimeout() {
231 final PluginContainerFailedEvent pluginContainerFailedEvent = mock(PluginContainerFailedEvent.class);
232 when(pluginContainerFailedEvent.getPluginKey()).thenReturn(PLUGIN_KEY);
233
234 _enablePlugin();
235 plugin.onPluginContainerFailed(pluginContainerFailedEvent);
236
237 assertThat(plugin.getPluginState(), is(PluginState.DISABLED));
238 assertThat(plugin.getBundle().getState(), is(Bundle.RESOLVED));
239 }
240
241 @RequiresRestart
242 public static class StaticModuleDescriptor extends AbstractModuleDescriptor<Object> {
243 public StaticModuleDescriptor() {
244 super(ModuleFactory.LEGACY_MODULE_FACTORY);
245 }
246
247 public Object getModule() {
248 return null;
249 }
250 }
251 }