SUPPORTED_CLASS_NAMES = Set.of(
- Set.of().getClass().getName(),
- Set.of(Integer.MAX_VALUE).getClass().getName(),
- Collections.emptySet().getClass().getName());
-
- public CustomSetConverter(Mapper mapper) {
- super(mapper);
- }
-
- @Override
- public boolean canConvert(Class type) {
- return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName());
- }
-
- @Override
- protected Object createCollection(Class type) {
- return new HashSet<>();
- }
-}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/DeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/DeepClone.java
index e77a4022cacb1..88668b9909833 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/DeepClone.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/DeepClone.java
@@ -1,11 +1,15 @@
package io.quarkus.test.junit.internal;
+import io.quarkus.bootstrap.app.RunningQuarkusApplication;
+
/**
* Strategy to deep clone an object
- *
+ *
* Used in order to clone an object loaded from one ClassLoader into another
*/
public interface DeepClone {
Object clone(Object objectToClone);
+
+ void setRunningQuarkusApplication(RunningQuarkusApplication runningQuarkusApplication);
}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java
new file mode 100644
index 0000000000000..b5afc517cf95b
--- /dev/null
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java
@@ -0,0 +1,144 @@
+package io.quarkus.test.junit.internal;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+
+import org.jboss.marshalling.cloner.ClassCloner;
+import org.jboss.marshalling.cloner.ClonerConfiguration;
+import org.jboss.marshalling.cloner.ObjectCloner;
+import org.jboss.marshalling.cloner.ObjectCloners;
+import org.junit.jupiter.api.TestInfo;
+
+import io.quarkus.bootstrap.app.RunningQuarkusApplication;
+
+/**
+ * A deep-clone implementation using JBoss Marshalling's fast object cloner.
+ */
+public final class NewSerializingDeepClone implements DeepClone {
+ private final ObjectCloner cloner;
+ private static Pattern clonePattern;
+ private RunningQuarkusApplication runningQuarkusApplication;
+
+ public NewSerializingDeepClone(final ClassLoader sourceLoader, final ClassLoader targetLoader) {
+ ClonerConfiguration cc = new ClonerConfiguration();
+ cc.setSerializabilityChecker(clazz -> clazz != Object.class);
+ cc.setClassCloner(new ClassCloner() {
+ public Class> clone(final Class> original) {
+ if (isUncloneable(original)) {
+ return original;
+ }
+ try {
+ return targetLoader.loadClass(original.getName());
+ } catch (ClassNotFoundException ignored) {
+ return original;
+ }
+ }
+
+ public Class> cloneProxy(final Class> proxyClass) {
+ // not really supported
+ return proxyClass;
+ }
+ });
+ cc.setCloneTable(
+ (original, objectCloner, classCloner) -> {
+ // The class we're really dealing with, which might be wrapped inside an array, or a nest of arrays
+ Class> theClassWeCareAbout = original.getClass();
+ while (theClassWeCareAbout.isArray()) {
+ theClassWeCareAbout = theClassWeCareAbout.getComponentType();
+ }
+
+ // Short-circuit the checks if we've been configured to clone this
+ if (!clonePattern.matcher(theClassWeCareAbout.getName()).matches()) {
+
+ if (theClassWeCareAbout.isPrimitive()) {
+ // avoid copying things that do not need to be copied
+ return original;
+ } else if (isUncloneable(theClassWeCareAbout)) {
+ if (original instanceof Supplier> s) {
+ // sneaky
+ return (Supplier>) () -> clone(s.get());
+ } else {
+ return original;
+ }
+ } else if (original instanceof TestInfo info) {
+ // copy the test info correctly
+ return new TestInfoImpl(info.getDisplayName(), info.getTags(),
+ info.getTestClass()
+ .map(this::cloneClass),
+ info.getTestMethod()
+ .map(this::cloneMethod));
+ } else {
+ try {
+ if (runningQuarkusApplication != null && runningQuarkusApplication.getClassLoader()
+ .loadClass(theClassWeCareAbout.getName()) == theClassWeCareAbout) {
+ // Don't clone things which are already loaded by the quarkus application's classloader side of the tree
+ return original;
+ }
+ } catch (ClassNotFoundException e) {
+
+ if (original instanceof Supplier> s) {
+ // sneaky
+ return (Supplier>) () -> clone(s.get());
+ } else {
+ throw e;
+ }
+ }
+
+ if (original == sourceLoader) {
+ return targetLoader;
+ }
+ }
+ }
+
+ // let the default cloner handle it
+ return null;
+ });
+ cloner = ObjectCloners.getSerializingObjectClonerFactory()
+ .createCloner(cc);
+ }
+
+ private static boolean isUncloneable(Class> clazz) {
+ return clazz.isHidden() && !Serializable.class.isAssignableFrom(clazz);
+ }
+
+ private Class> cloneClass(Class> clazz) {
+ try {
+ return (Class>) cloner.clone(clazz);
+ } catch (IOException | ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ private Method cloneMethod(Method method) {
+ try {
+ Class> declaring = (Class>) cloner.clone(method.getDeclaringClass());
+ Class>[] argTypes = (Class>[]) cloner.clone(method.getParameterTypes());
+ return declaring.getDeclaredMethod(method.getName(), argTypes);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public Object clone(final Object objectToClone) {
+ try {
+ return cloner.clone(objectToClone);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void setRunningQuarkusApplication(RunningQuarkusApplication runningQuarkusApplication) {
+ this.runningQuarkusApplication = runningQuarkusApplication;
+ String patternString = runningQuarkusApplication.getConfigValue("quarkus.test.class-clone-pattern", String.class)
+ .orElse("java\\..*");
+ clonePattern = Pattern.compile(patternString);
+ }
+
+}
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
deleted file mode 100644
index 3da2c0c16e372..0000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java
+++ /dev/null
@@ -1,46 +0,0 @@
-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
deleted file mode 100644
index 36da89a82e804..0000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java
+++ /dev/null
@@ -1,35 +0,0 @@
-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/TestInfoImpl.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java
similarity index 95%
rename from test-framework/junit5/src/main/java/io/quarkus/test/junit/TestInfoImpl.java
rename to test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java
index 498cc5ff64447..7cc0be697b719 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/TestInfoImpl.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java
@@ -1,4 +1,4 @@
-package io.quarkus.test.junit;
+package io.quarkus.test.junit.internal;
import java.lang.reflect.Method;
import java.util.Optional;
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
deleted file mode 100644
index 9951f96734d44..0000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package io.quarkus.test.junit.internal;
-
-import java.util.function.Supplier;
-
-import com.thoughtworks.xstream.XStream;
-
-/**
- * Super simple cloning strategy that just serializes to XML and deserializes it using xstream
- */
-class XStreamDeepClone implements DeepClone {
-
- private final Supplier xStreamSupplier;
-
- XStreamDeepClone(ClassLoader classLoader) {
- // avoid doing any work eagerly since the cloner is rarely used
- xStreamSupplier = () -> {
- XStream result = new XStream();
- result.allowTypesByRegExp(new String[] { ".*" });
- result.setClassLoader(classLoader);
- result.registerConverter(new CustomListConverter(result.getMapper()));
- result.registerConverter(new CustomSetConverter(result.getMapper()));
- result.registerConverter(new CustomMapConverter(result.getMapper()));
- result.registerConverter(new CustomMapEntryConverter(result.getMapper()));
-
- return result;
- };
- }
-
- @Override
- public Object clone(Object objectToClone) {
- if (objectToClone == null) {
- return null;
- }
-
- if (objectToClone instanceof Supplier) {
- return handleSupplier((Supplier>) objectToClone);
- }
-
- return doClone(objectToClone);
- }
-
- private Supplier