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