From cce6e1334c4d1401a08d56e77d1b3c03142faf98 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Thu, 2 Feb 2023 20:05:51 +0200 Subject: Improve class generation for testing (#515) --- .../java/org/pf4j/AbstractExtensionFinderTest.java | 112 ++++++++------------- .../java/org/pf4j/DefaultExtensionFactoryTest.java | 24 ++++- .../java/org/pf4j/DefaultPluginFactoryTest.java | 59 +++++++++-- .../org/pf4j/ExtensionAnnotationProcessorTest.java | 93 +++++++---------- .../org/pf4j/SingletonExtensionFactoryTest.java | 3 +- .../java/org/pf4j/test/AnotherFailTestPlugin.java | 32 ------ .../test/java/org/pf4j/test/AnotherTestPlugin.java | 34 ------- .../org/pf4j/test/DefaultClassDataProvider.java | 20 +--- .../test/java/org/pf4j/test/FailTestExtension.java | 37 ------- .../test/java/org/pf4j/test/FailTestPlugin.java | 26 ----- .../org/pf4j/test/JavaFileObjectClassLoader.java | 73 ++++++++++++++ .../org/pf4j/test/JavaFileObjectDataProvider.java | 62 ++++++++++++ .../java/org/pf4j/test/JavaFileObjectUtils.java | 52 ++++++++++ pf4j/src/test/java/org/pf4j/test/JavaSources.java | 64 ++++++++++++ pf4j/src/test/java/org/pf4j/test/TestPlugin.java | 3 +- 15 files changed, 409 insertions(+), 285 deletions(-) delete mode 100644 pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java delete mode 100644 pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java delete mode 100644 pf4j/src/test/java/org/pf4j/test/FailTestExtension.java delete mode 100644 pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java create mode 100644 pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java create mode 100644 pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java create mode 100644 pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java create mode 100644 pf4j/src/test/java/org/pf4j/test/JavaSources.java diff --git a/pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java b/pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java index eff6ebe..1e4b5eb 100644 --- a/pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java +++ b/pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java @@ -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; @@ -64,9 +56,9 @@ public class AbstractExtensionFinderTest { 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()); } @@ -93,7 +85,7 @@ public class AbstractExtensionFinderTest { } }; - List> list = instance.find(FailTestPlugin.class); + List> list = instance.find(TestExtension.class); assertEquals(0, list.size()); } @@ -115,7 +107,6 @@ public class AbstractExtensionFinderTest { Set bucket = new HashSet<>(); bucket.add("org.pf4j.test.TestExtension"); - bucket.add("org.pf4j.test.FailTestExtension"); entries.put(null, bucket); return entries; @@ -124,7 +115,7 @@ public class AbstractExtensionFinderTest { }; List> list = instance.find(TestExtensionPoint.class); - assertEquals(2, list.size()); + assertEquals(1, list.size()); } /** @@ -140,7 +131,6 @@ public class AbstractExtensionFinderTest { Set 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"); @@ -157,12 +147,13 @@ public class AbstractExtensionFinderTest { }; List> 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()); } @@ -210,6 +201,16 @@ public class AbstractExtensionFinderTest { */ @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 @@ -218,11 +219,13 @@ public class AbstractExtensionFinderTest { Set 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; } @@ -235,73 +238,40 @@ public class AbstractExtensionFinderTest { }; List plugin1Result = instance.find("plugin1"); - assertEquals(2, plugin1Result.size()); + assertEquals(1, plugin1Result.size()); List plugin2Result = instance.find("plugin2"); assertEquals(0, plugin2Result.size()); - List plugin3Result = instance.find(UUID.randomUUID().toString()); - assertEquals(0, plugin3Result.size()); + List plugin3Result = instance.find("plugin3"); + assertEquals(1, plugin3Result.size()); + + List 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 generatedFiles = compilation.generatedFiles(); + public void findExtensionAnnotation() { + List generatedFiles = JavaSources.compileAll(JavaSources.Greeting, JavaSources.WhazzupGreeting); assertEquals(2, generatedFiles.size()); - JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader(); - Map> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles)); + Map> 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 generatedFiles = JavaSources.compileAll(JavaSources.Greeting, ExtensionAnnotationProcessorTest.SpinnakerExtension_NoExtension, ExtensionAnnotationProcessorTest.WhazzupGreeting_SpinnakerExtension); - assertThat(compilation).succeededWithoutWarnings(); - ImmutableList generatedFiles = compilation.generatedFiles(); assertEquals(3, generatedFiles.size()); - JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader(); - Map> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles)); + Map> 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> loadClasses(List classes) throws IOException { - // Sort generated ".class" by lastModified field - classes.sort(Comparator.comparingLong(JavaFileObject::getLastModified)); - - // Load classes - Map> 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); } } diff --git a/pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java b/pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java index 9d73b5e..47eb2d3 100644 --- a/pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java +++ b/pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java @@ -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; @@ -29,6 +33,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows; */ 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 @@ -54,7 +71,10 @@ public class DefaultExtensionFactoryTest { */ @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)); } } diff --git a/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java b/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java index c453e38..24ed40c 100644 --- a/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java +++ b/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java @@ -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; @@ -33,6 +37,29 @@ import static org.mockito.Mockito.when; */ 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); @@ -52,27 +79,35 @@ public class DefaultPluginFactoryTest { @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(); @@ -98,11 +133,15 @@ public class DefaultPluginFactoryTest { @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(); diff --git a/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java b/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java index f966523..2b3241d 100644 --- a/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java +++ b/pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java @@ -16,12 +16,16 @@ package org.pf4j; import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pf4j.processor.ExtensionAnnotationProcessor; import org.pf4j.processor.LegacyExtensionStorage; +import org.pf4j.test.JavaSources; import javax.tools.JavaFileObject; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -38,30 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ public class ExtensionAnnotationProcessorTest { - public static final JavaFileObject Greeting = JavaFileObjects.forSourceLines( - "Greeting", - "package test;", - "import org.pf4j.ExtensionPoint;", - "", - "public interface Greeting extends ExtensionPoint {", - " String getGreeting();", - "}"); - - public static final JavaFileObject WhazzupGreeting = JavaFileObjects.forSourceLines( - "WhazzupGreeting", - "package test;", - "import org.pf4j.Extension;", - "", - "@Extension", - "public class WhazzupGreeting implements Greeting {", - " @Override", - " public String getGreeting() {", - " return \"Whazzup\";", - " }", - "}"); - - public static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines( - "WhazzupGreeting", + public static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines("WhazzupGreeting", "package test;", "import org.pf4j.Extension;", "", @@ -73,8 +54,7 @@ public class ExtensionAnnotationProcessorTest { " }", "}"); - public static final JavaFileObject SpinnakerExtension = JavaFileObjects.forSourceLines( - "SpinnakerExtension", + public static final JavaFileObject SpinnakerExtension = JavaFileObjects.forSourceLines("SpinnakerExtension", "package test;", "", "import org.pf4j.Extension;", @@ -91,8 +71,7 @@ public class ExtensionAnnotationProcessorTest { "public @interface SpinnakerExtension {", "}"); - public static final JavaFileObject WhazzupGreeting_SpinnakerExtension = JavaFileObjects.forSourceLines( - "WhazzupGreeting", + public static final JavaFileObject WhazzupGreeting_SpinnakerExtension = JavaFileObjects.forSourceLines("WhazzupGreeting", "package test;", "", "@SpinnakerExtension", @@ -104,10 +83,9 @@ public class ExtensionAnnotationProcessorTest { "}"); /** - * The same like {@link #SpinnakerExtension} but without {@code Extension} annotation. + * The same as {@link #SpinnakerExtension} but without {@code Extension} annotation. */ - public static final JavaFileObject SpinnakerExtension_NoExtension = JavaFileObjects.forSourceLines( - "SpinnakerExtension", + public static final JavaFileObject SpinnakerExtension_NoExtension = JavaFileObjects.forSourceLines("SpinnakerExtension", "package test;", "", "import org.pf4j.Extension;", @@ -124,52 +102,53 @@ public class ExtensionAnnotationProcessorTest { "public @interface SpinnakerExtension {", "}"); + private ExtensionAnnotationProcessor annotationProcessor; + + @BeforeEach + public void setUp() throws IOException { + annotationProcessor = new ExtensionAnnotationProcessor(); + } + @Test public void getSupportedAnnotationTypes() { - ExtensionAnnotationProcessor instance = new ExtensionAnnotationProcessor(); - Set result = instance.getSupportedAnnotationTypes(); + Set result = annotationProcessor.getSupportedAnnotationTypes(); assertEquals(1, result.size()); assertEquals("*", result.iterator().next()); } @Test public void getSupportedOptions() { - ExtensionAnnotationProcessor instance = new ExtensionAnnotationProcessor(); - Set result = instance.getSupportedOptions(); + Set result = annotationProcessor.getSupportedOptions(); assertEquals(2, result.size()); } @Test public void options() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).withOptions("-Ab=2", "-Ac=3") - .compile(Greeting, WhazzupGreeting); - assertEquals(compilation.status(), Compilation.Status.SUCCESS); + Compilation compilation = compiler().withOptions("-Ab=2", "-Ac=3") + .compile(JavaSources.Greeting, JavaSources.WhazzupGreeting); + assertEquals(Compilation.Status.SUCCESS, compilation.status()); Map options = new HashMap<>(); options.put("b", "2"); options.put("c", "3"); - assertEquals(options, processor.getProcessingEnvironment().getOptions()); + assertEquals(options, annotationProcessor.getProcessingEnvironment().getOptions()); } @Test public void storage() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting); - assertEquals(compilation.status(), Compilation.Status.SUCCESS); - assertEquals(processor.getStorage().getClass(), LegacyExtensionStorage.class); + Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting); + assertEquals(Compilation.Status.SUCCESS, compilation.status()); + assertEquals(annotationProcessor.getStorage().getClass(), LegacyExtensionStorage.class); } @Test public void compileWithoutError() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting); + Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting); assertThat(compilation).succeededWithoutWarnings(); } @Test public void compileWithError() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting_NoExtensionPoint); + Compilation compilation = compile(JavaSources.Greeting, WhazzupGreeting_NoExtensionPoint); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("it doesn't implement ExtensionPoint") .inFile(WhazzupGreeting_NoExtensionPoint) @@ -179,22 +158,28 @@ public class ExtensionAnnotationProcessorTest { @Test public void getExtensions() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting); + Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting); assertThat(compilation).succeededWithoutWarnings(); Map> extensions = new HashMap<>(); extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting"))); - assertEquals(extensions, processor.getExtensions()); + assertEquals(extensions, annotationProcessor.getExtensions()); } @Test public void compileNestedExtensionAnnotation() { - ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor(); - Compilation compilation = javac().withProcessors(processor).compile(Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension); + Compilation compilation = compile(JavaSources.Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension); assertThat(compilation).succeededWithoutWarnings(); Map> extensions = new HashMap<>(); extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting"))); - assertEquals(extensions, processor.getExtensions()); + assertEquals(extensions, annotationProcessor.getExtensions()); + } + + private Compiler compiler() { + return javac().withProcessors(annotationProcessor); + } + + private Compilation compile(JavaFileObject... sources) { + return compiler().compile(sources); } } diff --git a/pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java b/pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java index ddfd2a4..ff3d65a 100644 --- a/pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java +++ b/pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java @@ -18,7 +18,6 @@ package org.pf4j; 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.TestExtension; import java.io.File; @@ -57,7 +56,7 @@ public class SingletonExtensionFactoryTest { @Test public void createNewEachTime() { - ExtensionFactory extensionFactory = new SingletonExtensionFactory(pluginManager, FailTestExtension.class.getName()); + ExtensionFactory extensionFactory = new SingletonExtensionFactory(pluginManager, "FailTestExtension.class"); Object extensionOne = extensionFactory.create(TestExtension.class); Object extensionTwo = extensionFactory.create(TestExtension.class); assertNotSame(extensionOne, extensionTwo); diff --git a/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java b/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java deleted file mode 100644 index 79aa41b..0000000 --- a/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pf4j.test; - -import org.pf4j.Plugin; - -/** - * A wrong {@link org.pf4j.Plugin}. - * It's wrong because it calls super constructor with {@code null} for ({@link org.pf4j.PluginWrapper} parameter). - * - * @author Mario Franco - */ -public class AnotherFailTestPlugin extends Plugin { - - public AnotherFailTestPlugin() { - super(null); - } - -} diff --git a/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java b/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java deleted file mode 100644 index f2e10c1..0000000 --- a/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pf4j.test; - -import org.pf4j.Plugin; - -/** - * A simple {@link Plugin}. - * - * In real applications you don't need to create a plugin like this if you are not interested in lifecycle events. - * {@code PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified. - * - * @author Decebal Suiu - */ -public class AnotherTestPlugin extends Plugin { - - public AnotherTestPlugin() { - super(); - } - -} diff --git a/pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java b/pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java index 6a203aa..473f948 100644 --- a/pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java +++ b/pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java @@ -15,10 +15,10 @@ */ package org.pf4j.test; -import java.io.ByteArrayOutputStream; +import com.google.common.io.ByteStreams; + import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; /** * Get class data from the class path. @@ -35,20 +35,10 @@ public class DefaultClassDataProvider implements ClassDataProvider { throw new RuntimeException("Cannot find class data"); } - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - copyStream(classDataStream, outputStream); - return outputStream.toByteArray(); + try { + return ByteStreams.toByteArray(classDataStream); } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - - private void copyStream(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - - int bytesRead; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); + throw new IllegalStateException(e); } } diff --git a/pf4j/src/test/java/org/pf4j/test/FailTestExtension.java b/pf4j/src/test/java/org/pf4j/test/FailTestExtension.java deleted file mode 100644 index 1ce2aa8..0000000 --- a/pf4j/src/test/java/org/pf4j/test/FailTestExtension.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pf4j.test; - -import org.pf4j.Extension; - -/** - * A wrong {@link org.pf4j.Extension}. - * It's wrong because it doesn't contain a constructor with empty parameters (or only the default constructor). - * - * @author Mario Franco - */ -@Extension -public class FailTestExtension implements TestExtensionPoint { - - public FailTestExtension(String name) { - } - - @Override - public String saySomething() { - return "I am a fail test extension"; - } - -} diff --git a/pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java b/pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java deleted file mode 100644 index 3a1824f..0000000 --- a/pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pf4j.test; - -/** - * A wrong {@link org.pf4j.Plugin}. - * It's wrong because it doesn't extends {@link org.pf4j.Plugin}. - * - * @author Mario Franco - */ -public class FailTestPlugin { - -} diff --git a/pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java new file mode 100644 index 0000000..2afe0d2 --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.test; + +import javax.tools.JavaFileObject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * {@link ClassLoader} that loads {@link JavaFileObject.Kind#CLASS}s. + * If {@code JavaFileObject} type is {@link JavaFileObject.Kind#SOURCE} them the source is compiled. + * + * @author Decebal Suiu + */ +public class JavaFileObjectClassLoader extends ClassLoader { + + public Map> load(JavaFileObject... objects) { + return load(Arrays.asList(objects)); + } + + public Map> load(List objects) { + Objects.requireNonNull(objects); + + List mutableObjects = new ArrayList<>(objects); + + // Sort generated ".class" by lastModified field + mutableObjects.sort(Comparator.comparingLong(JavaFileObject::getLastModified)); + + // Compile Java sources (if exists) + for (int i = 0; i < mutableObjects.size(); i++) { + JavaFileObject object = mutableObjects.get(i); + if (object.getKind() == JavaFileObject.Kind.CLASS) { + continue; + } + + if (object.getKind() == JavaFileObject.Kind.SOURCE) { + mutableObjects.set(i, JavaSources.compile(object)); + } else { + throw new IllegalStateException("Type " + object.getKind() + " is not supported"); + } + } + + // Load objects + Map> loadedClasses = new HashMap<>(); + for (JavaFileObject object : mutableObjects) { + String className = JavaFileObjectUtils.getClassName(object); + byte[] data = JavaFileObjectUtils.getAllBytes(object); + Class loadedClass = defineClass(className, data, 0, data.length); + loadedClasses.put(className, loadedClass); + } + + return loadedClasses; + } + +} diff --git a/pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java new file mode 100644 index 0000000..d729721 --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.test; + +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Get class data from {@link JavaFileObject}. + * If {@code JavaFileObject} type is {@link JavaFileObject.Kind#SOURCE} them the source is compiled. + * + * @author Decebal Suiu + */ +public class JavaFileObjectDataProvider implements ClassDataProvider { + + private final Map classes; + + public JavaFileObjectDataProvider(Map classes) { + this.classes = classes; + } + + public static JavaFileObjectDataProvider of(List objects) { + List tmp = new ArrayList<>(objects.size()); + for (JavaFileObject object : objects) { + if (object.getKind() == JavaFileObject.Kind.CLASS) { + tmp.add(object); + } else if (object.getKind() == JavaFileObject.Kind.SOURCE) { + tmp.add(JavaSources.compile(object)); + } else { + throw new IllegalStateException("Type " + object.getKind() + " is not supported"); + } + } + + // TODO JavaFileObjectUtils.getClassName() ?! + Map classes = tmp.stream().collect(Collectors.toMap(FileObject::getName, c -> c)); + + return new JavaFileObjectDataProvider(classes); + } + + @Override + public byte[] getClassData(String className) { + return JavaFileObjectUtils.getAllBytes(classes.get(className)); + } + +} diff --git a/pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java new file mode 100644 index 0000000..4e7c32c --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.test; + +import com.google.common.io.ByteStreams; + +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Decebal Suiu + */ +public class JavaFileObjectUtils { + + private JavaFileObjectUtils() {} + + public static String getClassName(JavaFileObject object) { + if (object.getKind() != JavaFileObject.Kind.CLASS) { + throw new IllegalStateException("Only Kind.CLASS is supported"); + } + + 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; + } + + public static byte[] getAllBytes(JavaFileObject object) { + try (InputStream in = object.openInputStream()) { + return ByteStreams.toByteArray(in); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/pf4j/src/test/java/org/pf4j/test/JavaSources.java b/pf4j/src/test/java/org/pf4j/test/JavaSources.java new file mode 100644 index 0000000..2031edc --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/JavaSources.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.test; + +import com.google.testing.compile.JavaFileObjects; + +import javax.tools.JavaFileObject; +import java.util.List; + +import static com.google.testing.compile.Compiler.javac; + +/** + * Keep common Java sources (useful in many tests). + * For Java 13+ is recommended to use Text Block feature (it's more clear). + * + * @author Decebal Suiu + */ +public class JavaSources { + + public static final JavaFileObject Greeting = JavaFileObjects.forSourceLines("Greeting", + "package test;", + "import org.pf4j.ExtensionPoint;", + "", + "public interface Greeting extends ExtensionPoint {", + " String getGreeting();", + "}"); + + public static final JavaFileObject WhazzupGreeting = JavaFileObjects.forSourceLines("WhazzupGreeting", + "package test;", + "import org.pf4j.Extension;", + "", + "@Extension", + "public class WhazzupGreeting implements Greeting {", + " @Override", + " public String getGreeting() {", + " return \"Whazzup\";", + " }", + "}"); + + /** + * Compile a list of sources using javac compiler. + */ + public static List compileAll(JavaFileObject... sources) { + return javac().compile(sources).generatedFiles(); + } + + public static JavaFileObject compile(JavaFileObject source) { + return compileAll(source).get(0); + } + +} diff --git a/pf4j/src/test/java/org/pf4j/test/TestPlugin.java b/pf4j/src/test/java/org/pf4j/test/TestPlugin.java index bab85b3..59b818d 100644 --- a/pf4j/src/test/java/org/pf4j/test/TestPlugin.java +++ b/pf4j/src/test/java/org/pf4j/test/TestPlugin.java @@ -20,9 +20,8 @@ import org.pf4j.PluginWrapper; /** * A simple {@link Plugin}. - * * In real applications you don't need to create a plugin like this if you are not interested in lifecycle events. - * {@codes PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified. + * {@code PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified. * * @author Mario Franco */ -- cgit v1.2.3