From 945022ceefa26f3e04ac087c9fe9d2a98fd7a2f0 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Sun, 11 Apr 2021 19:49:51 +0200 Subject: [PATCH] Junit5 parameter: Introduce serialization alternative to XStream --- .../it/main/ParameterResolverTest.java | 78 ++++++++++++++++++- .../test/junit/QuarkusTestExtension.java | 5 +- .../internal/SerializationDeepClone.java | 46 +++++++++++ ...alizationWithXStreamFallbackDeepClone.java | 35 +++++++++ .../test/junit/internal/XStreamDeepClone.java | 5 +- 5 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/ParameterResolverTest.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ParameterResolverTest.java index 8a1d62b0fecdf..47030a4587dde 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/ParameterResolverTest.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ParameterResolverTest.java @@ -2,8 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.Serializable; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.function.Supplier; import javax.inject.Inject; @@ -19,14 +21,14 @@ import io.quarkus.test.junit.QuarkusTest; @QuarkusTest -@ExtendWith(ParameterResolverTest.UnusedBeanDummyInputResolver.class) -@ExtendWith(ParameterResolverTest.SupplierParameterResolver.class) public class ParameterResolverTest { @Inject UnusedBean unusedBean; @Test + @ExtendWith(ParameterResolverTest.UnusedBeanDummyInputResolver.class) + @ExtendWith(ParameterResolverTest.SupplierParameterResolver.class) public void testParameterResolver(UnusedBean.DummyInput dummyInput, Supplier supplier) { UnusedBean.DummyResult dummyResult = unusedBean.dummy(dummyInput); assertEquals("whatever/6", dummyResult.getResult()); @@ -35,6 +37,20 @@ public void testParameterResolver(UnusedBean.DummyInput dummyInput, Supplier list) { + assertEquals("foo", list.get(0).value); + assertEquals("bar", list.get(1).value); + } + public static class UnusedBeanDummyInputResolver implements ParameterResolver { @Override @@ -66,4 +82,62 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte } } + public static class SomeSerializableParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return SomeSerializable.class.getName().equals(parameterContext.getParameter().getType().getName()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return new SomeSerializable("foo", new SomeSerializable.SomeNestedSerializable("nested-foo")); + } + } + + public static class ListWithNonSerializableParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return List.class.isAssignableFrom(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return Arrays.asList(new NonSerializable("foo"), new NonSerializable("bar")); + } + } + + public static class SomeSerializable implements Serializable { + + private final String value; + private final SomeNestedSerializable nested; + + public SomeSerializable(String value, SomeNestedSerializable nested) { + this.value = value; + this.nested = nested; + } + + public static class SomeNestedSerializable implements Serializable { + + private final String value; + + public SomeNestedSerializable(String value) { + this.value = value; + } + } + } + + public static class NonSerializable { + + private final String value; + + public NonSerializable(String value) { + this.value = value; + } + } } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 3efc4f9523ba2..21106e58d3559 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -104,7 +104,7 @@ import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback; import io.quarkus.test.junit.callback.QuarkusTestMethodContext; import io.quarkus.test.junit.internal.DeepClone; -import io.quarkus.test.junit.internal.XStreamDeepClone; +import io.quarkus.test.junit.internal.SerializationWithXStreamFallbackDeepClone; public class QuarkusTestExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, InvocationInterceptor, AfterAllCallback, @@ -428,9 +428,8 @@ private List getAdditionalTestResources( } } - // keep it super simple for now, but we might need multiple strategies in the future private void populateDeepCloneField(StartupAction startupAction) { - deepClone = new XStreamDeepClone(startupAction.getClassLoader()); + deepClone = new SerializationWithXStreamFallbackDeepClone(startupAction.getClassLoader()); } private void populateCallbacks(ClassLoader classLoader) throws ClassNotFoundException { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java new file mode 100644 index 0000000000000..3da2c0c16e372 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java @@ -0,0 +1,46 @@ +package io.quarkus.test.junit.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; + +/** + * Cloning strategy that just serializes and deserializes using plain old java serialization. + */ +class SerializationDeepClone implements DeepClone { + + private final ClassLoader classLoader; + + SerializationDeepClone(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Object clone(Object objectToClone) { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(512); + try (ObjectOutputStream objOut = new ObjectOutputStream(byteOut)) { + objOut.writeObject(objectToClone); + try (ObjectInputStream objIn = new ClassLoaderAwareObjectInputStream(byteOut)) { + return objIn.readObject(); + } + } catch (IOException | ClassNotFoundException e) { + throw new IllegalStateException("Unable to deep clone object of type '" + objectToClone.getClass().getName() + + "'. Please report the issue on the Quarkus issue tracker.", e); + } + } + + private class ClassLoaderAwareObjectInputStream extends ObjectInputStream { + + public ClassLoaderAwareObjectInputStream(ByteArrayOutputStream byteOut) throws IOException { + super(new ByteArrayInputStream(byteOut.toByteArray())); + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { + return Class.forName(desc.getName(), true, classLoader); + } + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java new file mode 100644 index 0000000000000..36da89a82e804 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java @@ -0,0 +1,35 @@ +package io.quarkus.test.junit.internal; + +import java.io.Serializable; +import java.util.Optional; + +import org.jboss.logging.Logger; + +/** + * Cloning strategy delegating to {@link SerializationDeepClone}, falling back to {@link XStreamDeepClone} in case of error. + */ +public class SerializationWithXStreamFallbackDeepClone implements DeepClone { + + private static final Logger LOG = Logger.getLogger(SerializationWithXStreamFallbackDeepClone.class); + + private final SerializationDeepClone serializationDeepClone; + private final XStreamDeepClone xStreamDeepClone; + + public SerializationWithXStreamFallbackDeepClone(ClassLoader classLoader) { + this.serializationDeepClone = new SerializationDeepClone(classLoader); + this.xStreamDeepClone = new XStreamDeepClone(classLoader); + } + + @Override + public Object clone(Object objectToClone) { + if (objectToClone instanceof Serializable) { + try { + return serializationDeepClone.clone(objectToClone); + } catch (RuntimeException re) { + LOG.debugf("SerializationDeepClone failed (will fall back to XStream): %s", + Optional.ofNullable(re.getCause()).orElse(re)); + } + } + return xStreamDeepClone.clone(objectToClone); + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java index 82edbd3fc1f79..dc945e5bf7f7c 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java @@ -7,11 +7,11 @@ /** * Super simple cloning strategy that just serializes to XML and deserializes it using xstream */ -public class XStreamDeepClone implements DeepClone { +class XStreamDeepClone implements DeepClone { private final Supplier xStreamSupplier; - public XStreamDeepClone(ClassLoader classLoader) { + XStreamDeepClone(ClassLoader classLoader) { // avoid doing any work eagerly since the cloner is rarely used xStreamSupplier = () -> { XStream result = new XStream(); @@ -22,6 +22,7 @@ public XStreamDeepClone(ClassLoader classLoader) { }; } + @Override public Object clone(Object objectToClone) { if (objectToClone == null) { return null;