Skip to content

Commit 742587e

Browse files
test: expand PluginManagerTest with comprehensive coverage
- Add test plugin implementations for various scenarios - Add tests for successful plugin loading and lifecycle - Add tests for error scenarios (missing META-INF/services, startup failures) - Add tests for class loader isolation verification - Add tests for plugin start/stop ordering by priority - Add tests for multiple plugin loading - Use @tempdir for proper test isolation Coverage now includes: - Plugin discovery with empty/missing directories - Plugin loading with proper META-INF/services - Full lifecycle (configure -> start -> stop) - Priority-based initialization (BEFORE_HTTP_ON, AFTER_HTTP_ON) - Reverse-order shutdown - Class loader isolation between plugins - Error handling for invalid plugins Co-authored-by: Roberto Franchini <robfrank@users.noreply.github.com>
1 parent 17d14a6 commit 742587e

File tree

1 file changed

+357
-3
lines changed

1 file changed

+357
-3
lines changed

server/src/test/java/com/arcadedb/server/plugin/PluginManagerTest.java

Lines changed: 357 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,24 @@
2121
import com.arcadedb.ContextConfiguration;
2222
import com.arcadedb.GlobalConfiguration;
2323
import com.arcadedb.server.ArcadeDBServer;
24+
import com.arcadedb.server.ServerException;
2425
import com.arcadedb.server.ServerPlugin;
26+
import com.arcadedb.server.http.HttpServer;
27+
import io.undertow.server.handlers.PathHandler;
2528
import org.junit.jupiter.api.AfterEach;
2629
import org.junit.jupiter.api.BeforeEach;
2730
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.io.TempDir;
2832

29-
import java.io.File;
33+
import java.io.*;
34+
import java.nio.file.Files;
35+
import java.nio.file.Path;
3036
import java.util.Collection;
37+
import java.util.Set;
38+
import java.util.concurrent.atomic.AtomicBoolean;
39+
import java.util.concurrent.atomic.AtomicInteger;
40+
import java.util.jar.JarEntry;
41+
import java.util.jar.JarOutputStream;
3142

3243
import static org.junit.jupiter.api.Assertions.*;
3344

@@ -40,18 +51,24 @@ public class PluginManagerTest {
4051
private ArcadeDBServer server;
4152
private PluginManager pluginManager;
4253

54+
@TempDir
55+
Path tempDir;
56+
4357
@BeforeEach
4458
public void setup() {
4559
final ContextConfiguration configuration = new ContextConfiguration();
46-
configuration.setValue(GlobalConfiguration.SERVER_ROOT_PATH, "./target/test-server");
47-
configuration.setValue(GlobalConfiguration.SERVER_DATABASE_DIRECTORY, "./target/test-server/databases");
60+
configuration.setValue(GlobalConfiguration.SERVER_ROOT_PATH, tempDir.toString());
61+
configuration.setValue(GlobalConfiguration.SERVER_DATABASE_DIRECTORY, tempDir.resolve("databases").toString());
4862

4963
server = new ArcadeDBServer(configuration);
5064
pluginManager = new PluginManager(server, configuration);
5165
}
5266

5367
@AfterEach
5468
public void teardown() {
69+
if (pluginManager != null) {
70+
pluginManager.stopPlugins();
71+
}
5572
if (server != null && server.isStarted()) {
5673
server.stop();
5774
}
@@ -89,4 +106,341 @@ public void testStopPluginsWhenEmpty() {
89106
// Should handle stopping with no plugins loaded
90107
assertDoesNotThrow(() -> pluginManager.stopPlugins());
91108
}
109+
110+
@Test
111+
public void testDiscoverPluginsWithEmptyDirectory() throws IOException {
112+
// Create empty plugins directory
113+
final Path pluginsDir = tempDir.resolve("lib/plugins");
114+
Files.createDirectories(pluginsDir);
115+
116+
pluginManager.discoverPlugins();
117+
assertEquals(0, pluginManager.getPluginCount());
118+
}
119+
120+
@Test
121+
public void testLoadPluginWithMetaInfServices() throws Exception {
122+
// Create a test plugin JAR with proper META-INF/services
123+
final Path pluginsDir = tempDir.resolve("lib/plugins");
124+
Files.createDirectories(pluginsDir);
125+
126+
final File pluginJar = createTestPluginJar(pluginsDir, "test-plugin", TestPlugin1.class);
127+
128+
pluginManager.discoverPlugins();
129+
130+
assertEquals(1, pluginManager.getPluginCount());
131+
assertTrue(pluginManager.getPluginNames().contains("test-plugin"));
132+
133+
final Collection<ServerPlugin> plugins = pluginManager.getPlugins();
134+
assertEquals(1, plugins.size());
135+
}
136+
137+
@Test
138+
public void testLoadMultiplePlugins() throws Exception {
139+
final Path pluginsDir = tempDir.resolve("lib/plugins");
140+
Files.createDirectories(pluginsDir);
141+
142+
createTestPluginJar(pluginsDir, "plugin1", TestPlugin1.class);
143+
createTestPluginJar(pluginsDir, "plugin2", TestPlugin2.class);
144+
145+
pluginManager.discoverPlugins();
146+
147+
assertEquals(2, pluginManager.getPluginCount());
148+
final Set<String> names = pluginManager.getPluginNames();
149+
assertTrue(names.contains("plugin1"));
150+
assertTrue(names.contains("plugin2"));
151+
}
152+
153+
@Test
154+
public void testPluginLifecycle() throws Exception {
155+
final Path pluginsDir = tempDir.resolve("lib/plugins");
156+
Files.createDirectories(pluginsDir);
157+
158+
createTestPluginJar(pluginsDir, "lifecycle-plugin", LifecycleTestPlugin.class);
159+
160+
pluginManager.discoverPlugins();
161+
assertEquals(1, pluginManager.getPluginCount());
162+
163+
// Start the plugin
164+
pluginManager.startPlugins(ServerPlugin.INSTALLATION_PRIORITY.BEFORE_HTTP_ON);
165+
166+
// Verify plugin was configured and started
167+
final PluginDescriptor descriptor = pluginManager.getPluginDescriptor("lifecycle-plugin");
168+
assertNotNull(descriptor);
169+
assertTrue(descriptor.isStarted());
170+
assertTrue(descriptor.getPluginInstance() instanceof LifecycleTestPlugin);
171+
172+
final LifecycleTestPlugin plugin = (LifecycleTestPlugin) descriptor.getPluginInstance();
173+
assertTrue(plugin.configured.get());
174+
assertTrue(plugin.started.get());
175+
assertFalse(plugin.stopped.get());
176+
177+
// Stop the plugin
178+
pluginManager.stopPlugins();
179+
assertTrue(plugin.stopped.get());
180+
assertFalse(descriptor.isStarted());
181+
}
182+
183+
@Test
184+
public void testPluginStartOrderByPriority() throws Exception {
185+
final Path pluginsDir = tempDir.resolve("lib/plugins");
186+
Files.createDirectories(pluginsDir);
187+
188+
createTestPluginJar(pluginsDir, "before-plugin", BeforeHttpPlugin.class);
189+
createTestPluginJar(pluginsDir, "after-plugin", AfterHttpPlugin.class);
190+
191+
pluginManager.discoverPlugins();
192+
assertEquals(2, pluginManager.getPluginCount());
193+
194+
// Start BEFORE_HTTP_ON plugins
195+
pluginManager.startPlugins(ServerPlugin.INSTALLATION_PRIORITY.BEFORE_HTTP_ON);
196+
197+
PluginDescriptor beforeDesc = pluginManager.getPluginDescriptor("before-plugin");
198+
PluginDescriptor afterDesc = pluginManager.getPluginDescriptor("after-plugin");
199+
200+
assertTrue(beforeDesc.isStarted());
201+
assertFalse(afterDesc.isStarted());
202+
203+
// Start AFTER_HTTP_ON plugins
204+
pluginManager.startPlugins(ServerPlugin.INSTALLATION_PRIORITY.AFTER_HTTP_ON);
205+
206+
assertTrue(beforeDesc.isStarted());
207+
assertTrue(afterDesc.isStarted());
208+
}
209+
210+
@Test
211+
public void testPluginWithoutMetaInfServices() throws Exception {
212+
final Path pluginsDir = tempDir.resolve("lib/plugins");
213+
Files.createDirectories(pluginsDir);
214+
215+
// Create JAR without META-INF/services
216+
final File pluginJar = pluginsDir.resolve("invalid-plugin.jar").toFile();
217+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(pluginJar))) {
218+
// Just create an empty JAR
219+
jos.putNextEntry(new JarEntry("dummy.txt"));
220+
jos.write("test".getBytes());
221+
jos.closeEntry();
222+
}
223+
224+
pluginManager.discoverPlugins();
225+
226+
// Plugin should not be loaded due to missing META-INF/services
227+
assertEquals(0, pluginManager.getPluginCount());
228+
}
229+
230+
@Test
231+
public void testPluginStartException() throws Exception {
232+
final Path pluginsDir = tempDir.resolve("lib/plugins");
233+
Files.createDirectories(pluginsDir);
234+
235+
createTestPluginJar(pluginsDir, "failing-plugin", FailingPlugin.class);
236+
237+
pluginManager.discoverPlugins();
238+
assertEquals(1, pluginManager.getPluginCount());
239+
240+
// Starting the plugin should throw exception
241+
assertThrows(ServerException.class, () ->
242+
pluginManager.startPlugins(ServerPlugin.INSTALLATION_PRIORITY.BEFORE_HTTP_ON));
243+
}
244+
245+
@Test
246+
public void testGetPluginDescriptor() throws Exception {
247+
final Path pluginsDir = tempDir.resolve("lib/plugins");
248+
Files.createDirectories(pluginsDir);
249+
250+
createTestPluginJar(pluginsDir, "test-plugin", TestPlugin1.class);
251+
252+
pluginManager.discoverPlugins();
253+
254+
final PluginDescriptor descriptor = pluginManager.getPluginDescriptor("test-plugin");
255+
assertNotNull(descriptor);
256+
assertEquals("test-plugin", descriptor.getPluginName());
257+
assertNotNull(descriptor.getPluginJarFile());
258+
assertNotNull(descriptor.getClassLoader());
259+
assertNotNull(descriptor.getPluginInstance());
260+
assertFalse(descriptor.isStarted());
261+
}
262+
263+
@Test
264+
public void testClassLoaderIsolation() throws Exception {
265+
final Path pluginsDir = tempDir.resolve("lib/plugins");
266+
Files.createDirectories(pluginsDir);
267+
268+
createTestPluginJar(pluginsDir, "plugin1", TestPlugin1.class);
269+
createTestPluginJar(pluginsDir, "plugin2", TestPlugin2.class);
270+
271+
pluginManager.discoverPlugins();
272+
273+
final PluginDescriptor desc1 = pluginManager.getPluginDescriptor("plugin1");
274+
final PluginDescriptor desc2 = pluginManager.getPluginDescriptor("plugin2");
275+
276+
// Each plugin should have its own class loader
277+
assertNotNull(desc1.getClassLoader());
278+
assertNotNull(desc2.getClassLoader());
279+
assertNotSame(desc1.getClassLoader(), desc2.getClassLoader());
280+
281+
// Both should be PluginClassLoader instances
282+
assertTrue(desc1.getClassLoader() instanceof PluginClassLoader);
283+
assertTrue(desc2.getClassLoader() instanceof PluginClassLoader);
284+
}
285+
286+
@Test
287+
public void testStopPluginsReverseOrder() throws Exception {
288+
final Path pluginsDir = tempDir.resolve("lib/plugins");
289+
Files.createDirectories(pluginsDir);
290+
291+
createTestPluginJar(pluginsDir, "plugin1", OrderTestPlugin1.class);
292+
createTestPluginJar(pluginsDir, "plugin2", OrderTestPlugin2.class);
293+
createTestPluginJar(pluginsDir, "plugin3", OrderTestPlugin3.class);
294+
295+
OrderTestPlugin1.stopOrder.set(0);
296+
OrderTestPlugin2.stopOrder.set(0);
297+
OrderTestPlugin3.stopOrder.set(0);
298+
OrderTestPlugin1.stopCounter.set(0);
299+
OrderTestPlugin2.stopCounter.set(0);
300+
OrderTestPlugin3.stopCounter.set(0);
301+
302+
pluginManager.discoverPlugins();
303+
pluginManager.startPlugins(ServerPlugin.INSTALLATION_PRIORITY.BEFORE_HTTP_ON);
304+
305+
// Stop plugins - should be in reverse order of discovery
306+
pluginManager.stopPlugins();
307+
308+
// Verify plugins were stopped in reverse order
309+
assertTrue(OrderTestPlugin3.stopOrder.get() < OrderTestPlugin2.stopOrder.get());
310+
assertTrue(OrderTestPlugin2.stopOrder.get() < OrderTestPlugin1.stopOrder.get());
311+
}
312+
313+
/**
314+
* Helper method to create a test plugin JAR with proper META-INF/services
315+
*/
316+
private File createTestPluginJar(final Path pluginsDir, final String pluginName, final Class<? extends ServerPlugin> pluginClass)
317+
throws Exception {
318+
final File jarFile = pluginsDir.resolve(pluginName + ".jar").toFile();
319+
320+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
321+
// Add the plugin class
322+
final String classFileName = pluginClass.getName().replace('.', '/') + ".class";
323+
jos.putNextEntry(new JarEntry(classFileName));
324+
325+
// Load class bytes from current classloader
326+
try (InputStream is = getClass().getClassLoader().getResourceAsStream(classFileName)) {
327+
if (is != null) {
328+
is.transferTo(jos);
329+
}
330+
}
331+
jos.closeEntry();
332+
333+
// Add META-INF/services/com.arcadedb.server.ServerPlugin
334+
jos.putNextEntry(new JarEntry("META-INF/services/com.arcadedb.server.ServerPlugin"));
335+
jos.write(pluginClass.getName().getBytes());
336+
jos.closeEntry();
337+
}
338+
339+
return jarFile;
340+
}
341+
342+
// Test plugin implementations
343+
public static class TestPlugin1 implements ServerPlugin {
344+
@Override
345+
public void startService() {
346+
}
347+
}
348+
349+
public static class TestPlugin2 implements ServerPlugin {
350+
@Override
351+
public void startService() {
352+
}
353+
}
354+
355+
public static class LifecycleTestPlugin implements ServerPlugin {
356+
public final AtomicBoolean configured = new AtomicBoolean(false);
357+
public final AtomicBoolean started = new AtomicBoolean(false);
358+
public final AtomicBoolean stopped = new AtomicBoolean(false);
359+
360+
@Override
361+
public void configure(ArcadeDBServer arcadeDBServer, ContextConfiguration configuration) {
362+
configured.set(true);
363+
}
364+
365+
@Override
366+
public void startService() {
367+
started.set(true);
368+
}
369+
370+
@Override
371+
public void stopService() {
372+
stopped.set(true);
373+
}
374+
}
375+
376+
public static class BeforeHttpPlugin implements ServerPlugin {
377+
@Override
378+
public void startService() {
379+
}
380+
381+
@Override
382+
public INSTALLATION_PRIORITY getInstallationPriority() {
383+
return INSTALLATION_PRIORITY.BEFORE_HTTP_ON;
384+
}
385+
}
386+
387+
public static class AfterHttpPlugin implements ServerPlugin {
388+
@Override
389+
public void startService() {
390+
}
391+
392+
@Override
393+
public INSTALLATION_PRIORITY getInstallationPriority() {
394+
return INSTALLATION_PRIORITY.AFTER_HTTP_ON;
395+
}
396+
}
397+
398+
public static class FailingPlugin implements ServerPlugin {
399+
@Override
400+
public void startService() {
401+
throw new RuntimeException("Plugin failed to start");
402+
}
403+
}
404+
405+
public static class OrderTestPlugin1 implements ServerPlugin {
406+
public static final AtomicInteger stopCounter = new AtomicInteger(0);
407+
public static final AtomicInteger stopOrder = new AtomicInteger(0);
408+
409+
@Override
410+
public void startService() {
411+
}
412+
413+
@Override
414+
public void stopService() {
415+
stopOrder.set(stopCounter.incrementAndGet());
416+
}
417+
}
418+
419+
public static class OrderTestPlugin2 implements ServerPlugin {
420+
public static final AtomicInteger stopCounter = new AtomicInteger(0);
421+
public static final AtomicInteger stopOrder = new AtomicInteger(0);
422+
423+
@Override
424+
public void startService() {
425+
}
426+
427+
@Override
428+
public void stopService() {
429+
stopOrder.set(stopCounter.incrementAndGet());
430+
}
431+
}
432+
433+
public static class OrderTestPlugin3 implements ServerPlugin {
434+
public static final AtomicInteger stopCounter = new AtomicInteger(0);
435+
public static final AtomicInteger stopOrder = new AtomicInteger(0);
436+
437+
@Override
438+
public void startService() {
439+
}
440+
441+
@Override
442+
public void stopService() {
443+
stopOrder.set(stopCounter.incrementAndGet());
444+
}
445+
}
92446
}

0 commit comments

Comments
 (0)