Skip to content

Commit

Permalink
Improve class generation for testing (pf4j#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
decebals authored Feb 2, 2023
1 parent 21a2854 commit cce6e13
Show file tree
Hide file tree
Showing 15 changed files with 409 additions and 285 deletions.
112 changes: 41 additions & 71 deletions pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,26 @@
*/
package org.pf4j;

import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.testing.compile.Compilation;
import java.util.Comparator;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.test.FailTestPlugin;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaFileObjectUtils;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestExtension;
import org.pf4j.test.TestExtensionPoint;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand All @@ -64,9 +56,9 @@ public void setUp() {
when(pluginStopped.getPluginState()).thenReturn(PluginState.STOPPED);

pluginManager = mock(PluginManager.class);
when(pluginManager.getPlugin(eq("plugin1"))).thenReturn(pluginStarted);
when(pluginManager.getPlugin(eq("plugin2"))).thenReturn(pluginStopped);
when(pluginManager.getPluginClassLoader(eq("plugin1"))).thenReturn(getClass().getClassLoader());
when(pluginManager.getPlugin("plugin1")).thenReturn(pluginStarted);
when(pluginManager.getPlugin("plugin2")).thenReturn(pluginStopped);
when(pluginManager.getPluginClassLoader("plugin1")).thenReturn(getClass().getClassLoader());
when(pluginManager.getExtensionFactory()).thenReturn(new DefaultExtensionFactory());
}

Expand All @@ -93,7 +85,7 @@ public Map<String, Set<String>> readClasspathStorages() {
}

};
List<ExtensionWrapper<FailTestPlugin>> list = instance.find(FailTestPlugin.class);
List<ExtensionWrapper<TestExtension>> list = instance.find(TestExtension.class);
assertEquals(0, list.size());
}

Expand All @@ -115,7 +107,6 @@ public Map<String, Set<String>> readClasspathStorages() {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put(null, bucket);

return entries;
Expand All @@ -124,7 +115,7 @@ public Map<String, Set<String>> readClasspathStorages() {
};

List<ExtensionWrapper<TestExtensionPoint>> list = instance.find(TestExtensionPoint.class);
assertEquals(2, list.size());
assertEquals(1, list.size());
}

/**
Expand All @@ -140,7 +131,6 @@ public Map<String, Set<String>> readPluginsStorages() {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put("plugin1", bucket);
bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
Expand All @@ -157,12 +147,13 @@ public Map<String, Set<String>> readClasspathStorages() {
};

List<ExtensionWrapper<TestExtensionPoint>> list = instance.find(TestExtensionPoint.class);
assertEquals(2, list.size());
assertEquals(1, list.size());

list = instance.find(TestExtensionPoint.class, "plugin1");
assertEquals(2, list.size());
assertEquals(1, list.size());

list = instance.find(TestExtensionPoint.class, "plugin2");
// "0" because the status of "plugin2" is STOPPED => no extensions
assertEquals(0, list.size());
}

Expand Down Expand Up @@ -210,6 +201,16 @@ public Map<String, Set<String>> readClasspathStorages() {
*/
@Test
public void testFindExtensionWrappersFromPluginId() {
// complicate the test to show hot to deal with dynamic Java classes (generated at runtime from sources)
PluginWrapper plugin3 = mock(PluginWrapper.class);
JavaFileObject object = JavaSources.compile(DefaultExtensionFactoryTest.FailTestExtension);
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(object);
when(plugin3.getPluginClassLoader()).thenReturn(classLoader);
when(plugin3.getPluginState()).thenReturn(PluginState.STARTED);
when(pluginManager.getPluginClassLoader("plugin3")).thenReturn(classLoader);
when(pluginManager.getPlugin("plugin3")).thenReturn(plugin3);

ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {

@Override
Expand All @@ -218,11 +219,13 @@ public Map<String, Set<String>> readPluginsStorages() {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put("plugin1", bucket);
bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
entries.put("plugin2", bucket);
bucket = new HashSet<>();
bucket.add(JavaFileObjectUtils.getClassName(object));
entries.put("plugin3", bucket);

return entries;
}
Expand All @@ -235,73 +238,40 @@ public Map<String, Set<String>> readClasspathStorages() {
};

List<ExtensionWrapper> plugin1Result = instance.find("plugin1");
assertEquals(2, plugin1Result.size());
assertEquals(1, plugin1Result.size());

List<ExtensionWrapper> plugin2Result = instance.find("plugin2");
assertEquals(0, plugin2Result.size());

List<ExtensionWrapper> plugin3Result = instance.find(UUID.randomUUID().toString());
assertEquals(0, plugin3Result.size());
List<ExtensionWrapper> plugin3Result = instance.find("plugin3");
assertEquals(1, plugin3Result.size());

List<ExtensionWrapper> plugin4Result = instance.find(UUID.randomUUID().toString());
assertEquals(0, plugin4Result.size());
}

@Test
public void findExtensionAnnotation() throws Exception {
Compilation compilation = javac().compile(ExtensionAnnotationProcessorTest.Greeting,
ExtensionAnnotationProcessorTest.WhazzupGreeting);
assertThat(compilation).succeededWithoutWarnings();
ImmutableList<JavaFileObject> generatedFiles = compilation.generatedFiles();
public void findExtensionAnnotation() {
List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertEquals(2, generatedFiles.size());

JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Map<String, Class<?>> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles));
Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
assertNotNull(extension);
Assertions.assertNotNull(extension);
}

@Test
public void findExtensionAnnotationThatMissing() throws Exception {
Compilation compilation = javac().compile(ExtensionAnnotationProcessorTest.Greeting,
public void findExtensionAnnotationThatMissing() {
List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting,
ExtensionAnnotationProcessorTest.SpinnakerExtension_NoExtension,
ExtensionAnnotationProcessorTest.WhazzupGreeting_SpinnakerExtension);
assertThat(compilation).succeededWithoutWarnings();
ImmutableList<JavaFileObject> generatedFiles = compilation.generatedFiles();
assertEquals(3, generatedFiles.size());

JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Map<String, Class<?>> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles));
Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
assertNull(extension);
}

static class JavaFileObjectClassLoader extends ClassLoader {

public Map<String, Class<?>> loadClasses(List<JavaFileObject> classes) throws IOException {
// Sort generated ".class" by lastModified field
classes.sort(Comparator.comparingLong(JavaFileObject::getLastModified));

// Load classes
Map<String, Class<?>> loadedClasses = new HashMap<>(classes.size());
for (JavaFileObject clazz : classes) {
String className = getClassName(clazz);
byte[] data = ByteStreams.toByteArray(clazz.openInputStream());
Class<?> loadedClass = defineClass(className, data,0, data.length);
loadedClasses.put(className, loadedClass);
}

return loadedClasses;
}

private static String getClassName(JavaFileObject object) {
String name = object.getName();
// Remove "/CLASS_OUT/" from head and ".class" from tail
name = name.substring(14, name.length() - 6);
name = name.replace('/', '.');

return name;
}

Assertions.assertNull(extension);
}

}
24 changes: 22 additions & 2 deletions pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
*/
package org.pf4j;

import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.test.FailTestExtension;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestExtension;

import javax.tools.JavaFileObject;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand All @@ -29,6 +33,19 @@
*/
public class DefaultExtensionFactoryTest {

public static final JavaFileObject FailTestExtension = JavaFileObjects.forSourceLines("FailTestExtension",
"package test;",
"import org.pf4j.test.TestExtensionPoint;",
"import org.pf4j.Extension;",
"",
"@Extension",
"public class FailTestExtension implements TestExtensionPoint {",
" public FailTestExtension(String name) {}",
"",
" @Override",
" public String saySomething() { return \"I am a fail test extension\";}",
"}");

private ExtensionFactory extensionFactory;

@BeforeEach
Expand All @@ -54,7 +71,10 @@ public void testCreate() {
*/
@Test
public void testCreateFailConstructor() {
assertThrows(PluginRuntimeException.class, () -> extensionFactory.create(FailTestExtension.class));
JavaFileObject object = JavaSources.compile(FailTestExtension);
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Class<?> extensionClass = (Class<?>) classLoader.load(object).values().toArray()[0];
assertThrows(PluginRuntimeException.class, () -> extensionFactory.create(extensionClass));
}

}
59 changes: 49 additions & 10 deletions pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
*/
package org.pf4j;

import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.Test;
import org.pf4j.test.AnotherFailTestPlugin;
import org.pf4j.test.AnotherTestPlugin;
import org.pf4j.test.FailTestPlugin;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaFileObjectUtils;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestPlugin;

import javax.tools.JavaFileObject;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
Expand All @@ -33,6 +37,29 @@
*/
public class DefaultPluginFactoryTest {

public static final JavaFileObject FailTestPlugin = JavaFileObjects.forSourceLines("FailTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class FailTestPlugin {",
"}");

public static final JavaFileObject AnotherFailTestPlugin = JavaFileObjects.forSourceLines("AnotherFailTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class AnotherFailTestPlugin extends Plugin {",
" public AnotherFailTestPlugin() { super(null); }",
"}");

public static final JavaFileObject AnotherTestPlugin = JavaFileObjects.forSourceLines("AnotherTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class AnotherTestPlugin extends Plugin {",
" public AnotherTestPlugin() { super(); }",
"}");

@Test
public void testCreate() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
Expand All @@ -52,27 +79,35 @@ public void testCreate() {
@Test
public void pluginConstructorNoParameters() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
when(pluginDescriptor.getPluginClass()).thenReturn(AnotherTestPlugin.class.getName());
JavaFileObject object = JavaSources.compile(AnotherTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(AnotherTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();

Plugin result = pluginFactory.create(pluginWrapper);
assertNotNull(result);
assertThat(result, instanceOf(AnotherTestPlugin.class));
assertEquals(pluginClassName, result.getClass().getName());
}

@Test
public void testCreateFail() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
when(pluginDescriptor.getPluginClass()).thenReturn(FailTestPlugin.class.getName());
JavaFileObject object = JavaSources.compile(FailTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(FailTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();

Expand All @@ -98,11 +133,15 @@ public void testCreateFailNotFound() {
@Test
public void testCreateFailConstructor() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
when(pluginDescriptor.getPluginClass()).thenReturn(AnotherFailTestPlugin.class.getName());
JavaFileObject object = JavaSources.compile(AnotherFailTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(AnotherFailTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();

Expand Down
Loading

0 comments on commit cce6e13

Please sign in to comment.