diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 82635f6f91c337..764d26301515be 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -4848,6 +4848,11 @@
pom
+
+ org.jboss.marshalling
+ jboss-marshalling
+ ${jboss-marshalling.version}
+
org.jboss.threads
jboss-threads
diff --git a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java
index 6219d4c8ca510d..83355e35ca9469 100644
--- a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java
+++ b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java
@@ -7,7 +7,6 @@
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
@@ -22,7 +21,6 @@
* mvn install -Dit.test=DevMojoIT#methodName
*/
@DisabledIfSystemProperty(named = "quarkus.test.native", matches = "true")
-@Disabled("Needs https://github.com/junit-team/junit5/pull/3820 and #40601")
public class TestParameterDevModeIT extends RunAndCheckMojoTestBase {
protected int getPort() {
diff --git a/test-framework/junit5/pom.xml b/test-framework/junit5/pom.xml
index 132c4db1b6531b..449f8fda37df57 100644
--- a/test-framework/junit5/pom.xml
+++ b/test-framework/junit5/pom.xml
@@ -49,10 +49,8 @@
quarkus-core
- com.thoughtworks.xstream
- xstream
-
- 1.4.20
+ org.jboss.marshalling
+ jboss-marshalling
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 da4a434c123b7f..ae707cbba745ae 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
@@ -106,7 +106,8 @@
import io.quarkus.test.junit.callback.QuarkusTestContext;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
import io.quarkus.test.junit.internal.DeepClone;
-import io.quarkus.test.junit.internal.SerializationWithXStreamFallbackDeepClone;
+import io.quarkus.test.junit.internal.NewSerializingDeepClone;
+import io.quarkus.test.junit.internal.TestInfoImpl;
public class QuarkusTestExtension extends AbstractJvmQuarkusTestExtension
implements BeforeEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterEachCallback,
@@ -351,7 +352,7 @@ private void shutdownHangDetection() {
}
private void populateDeepCloneField(StartupAction startupAction) {
- deepClone = new SerializationWithXStreamFallbackDeepClone(startupAction.getClassLoader());
+ deepClone = new NewSerializingDeepClone(originalCl, startupAction.getClassLoader());
}
private void populateTestMethodInvokers(ClassLoader quarkusClassLoader) {
@@ -979,6 +980,7 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation
} else if (clonePattern.matcher(theclass.getName()).matches()) {
cloneRequired = true;
} else {
+ // Don't clone things which are already loaded by the quarkus application's classloader side of the tree
try {
cloneRequired = runningQuarkusApplication.getClassLoader()
.loadClass(theclass.getName()) != theclass;
@@ -1002,6 +1004,7 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation
} else {
argumentsFromTccl.add(arg);
}
+
}
if (testMethodInvokerToUse != null) {
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java
deleted file mode 100644
index ddb8642d0056c5..00000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package io.quarkus.test.junit.internal;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
-
-import com.thoughtworks.xstream.converters.collections.CollectionConverter;
-import com.thoughtworks.xstream.mapper.Mapper;
-
-/**
- * A custom List converter that always uses ArrayList for unmarshalling.
- * This is probably not semantically correct 100% of the time, but it's likely fine
- * for all the cases where we are using marshalling / unmarshalling.
- *
- * The reason for doing this is to avoid XStream causing illegal access issues
- * for internal JDK lists
- */
-public class CustomListConverter extends CollectionConverter {
-
- // if we wanted to be 100% sure, we'd list all the List.of methods, but I think it's pretty safe to say
- // that the JDK won't add custom implementations for the other classes
-
- private final Predicate supported = new Predicate() {
-
- private final Set JDK_LIST_CLASS_NAMES = Set.of(
- List.of().getClass().getName(),
- List.of(Integer.MAX_VALUE).getClass().getName(),
- Arrays.asList(Integer.MAX_VALUE).getClass().getName(),
- Collections.unmodifiableList(List.of()).getClass().getName(),
- Collections.emptyList().getClass().getName(),
- List.of(Integer.MIN_VALUE, Integer.MAX_VALUE).subList(0, 1).getClass().getName());
-
- @Override
- public boolean test(String className) {
- return JDK_LIST_CLASS_NAMES.contains(className);
- }
- }.or(new Predicate<>() {
-
- private static final String GUAVA_LISTS_PACKAGE = "com.google.common.collect.Lists";
-
- @Override
- public boolean test(String className) {
- return className.startsWith(GUAVA_LISTS_PACKAGE);
- }
- });
-
- public CustomListConverter(Mapper mapper) {
- super(mapper);
- }
-
- @Override
- public boolean canConvert(Class type) {
- return (type != null) && supported.test(type.getName());
- }
-
- @Override
- protected Object createCollection(Class type) {
- return new ArrayList<>();
- }
-}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java
deleted file mode 100644
index fe93cb85945876..00000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package io.quarkus.test.junit.internal;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import com.thoughtworks.xstream.converters.collections.MapConverter;
-import com.thoughtworks.xstream.mapper.Mapper;
-
-/**
- * A custom Map converter that always uses HashMap for unmarshalling.
- * This is probably not semantically correct 100% of the time, but it's likely fine
- * for all the cases where we are using marshalling / unmarshalling.
- *
- * The reason for doing this is to avoid XStream causing illegal access issues
- * for internal JDK maps
- */
-public class CustomMapConverter extends MapConverter {
-
- // if we wanted to be 100% sure, we'd list all the Set.of methods, but I think it's pretty safe to say
- // that the JDK won't add custom implementations for the other classes
- private final Set SUPPORTED_CLASS_NAMES = Set.of(
- Map.of().getClass().getName(),
- Map.of(Integer.MAX_VALUE, Integer.MAX_VALUE).getClass().getName(),
- Collections.emptyMap().getClass().getName());
-
- public CustomMapConverter(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 HashMap<>();
- }
-}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java
deleted file mode 100644
index f20a7fe3e3f366..00000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.quarkus.test.junit.internal;
-
-import java.util.AbstractMap;
-import java.util.Map;
-import java.util.Set;
-
-import com.thoughtworks.xstream.converters.MarshallingContext;
-import com.thoughtworks.xstream.converters.UnmarshallingContext;
-import com.thoughtworks.xstream.converters.collections.MapConverter;
-import com.thoughtworks.xstream.io.HierarchicalStreamReader;
-import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
-import com.thoughtworks.xstream.mapper.Mapper;
-
-/**
- * A custom Map.Entry converter that always uses AbstractMap.SimpleEntry for unmarshalling.
- * This is probably not semantically correct 100% of the time, but it's likely fine
- * for all the cases where we are using marshalling / unmarshalling.
- *
- * The reason for doing this is to avoid XStream causing illegal access issues
- * for internal JDK types
- */
-@SuppressWarnings({ "rawtypes", "unchecked" })
-public class CustomMapEntryConverter extends MapConverter {
-
- private final Set SUPPORTED_CLASS_NAMES = Set
- .of(Map.entry(Integer.MAX_VALUE, Integer.MAX_VALUE).getClass().getName());
-
- public CustomMapEntryConverter(Mapper mapper) {
- super(mapper);
- }
-
- @Override
- public boolean canConvert(Class type) {
- return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName());
- }
-
- @Override
- public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
- var entryName = mapper().serializedClass(Map.Entry.class);
- var entry = (Map.Entry) source;
- writer.startNode(entryName);
- writeCompleteItem(entry.getKey(), context, writer);
- writeCompleteItem(entry.getValue(), context, writer);
- writer.endNode();
- }
-
- @Override
- public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
- reader.moveDown();
- var key = readCompleteItem(reader, context, null);
- var value = readCompleteItem(reader, context, null);
- reader.moveUp();
- return new AbstractMap.SimpleEntry(key, value);
- }
-}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java
deleted file mode 100644
index 88d434cfaf34a7..00000000000000
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.quarkus.test.junit.internal;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import com.thoughtworks.xstream.converters.collections.CollectionConverter;
-import com.thoughtworks.xstream.mapper.Mapper;
-
-/**
- * A custom Set converter that always uses HashSet for unmarshalling.
- * This is probably not semantically correct 100% of the time, but it's likely fine
- * for all the cases where we are using marshalling / unmarshalling.
- *
- * The reason for doing this is to avoid XStream causing illegal access issues
- * for internal JDK sets
- */
-public class CustomSetConverter extends CollectionConverter {
-
- // if we wanted to be 100% sure, we'd list all the Set.of methods, but I think it's pretty safe to say
- // that the JDK won't add custom implementations for the other classes
- private final Set 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/NewSerializingDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java
new file mode 100644
index 00000000000000..682a196e00c718
--- /dev/null
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java
@@ -0,0 +1,113 @@
+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.Set;
+import java.util.function.Supplier;
+
+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;
+
+/**
+ * A deep-clone implementation using JBoss Marshalling's fast object cloner.
+ */
+public final class NewSerializingDeepClone implements DeepClone {
+ private final ObjectCloner cloner;
+
+ 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) -> {
+ if (EXTRA_IDENTITY_CLASSES.contains(original.getClass())) {
+ // avoid copying things that do not need to be copied
+ return original;
+ } else if (isUncloneable(original.getClass())) {
+ 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 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);
+ }
+ }
+
+ /**
+ * Classes which do not need to be cloned.
+ */
+ private static final Set> EXTRA_IDENTITY_CLASSES = Set.of(
+ Object.class,
+ byte[].class,
+ short[].class,
+ int[].class,
+ long[].class,
+ char[].class,
+ boolean[].class,
+ float[].class,
+ double[].class);
+}
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 3da2c0c16e3725..00000000000000
--- 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 36da89a82e804f..00000000000000
--- 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 79%
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 498cc5ff644477..3f7a81b4d3b664 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;
@@ -6,14 +6,14 @@
import org.junit.jupiter.api.TestInfo;
-class TestInfoImpl implements TestInfo {
+public class TestInfoImpl implements TestInfo {
private final String displayName;
private final Set tags;
private final Optional> testClass;
private final Optional testMethod;
- TestInfoImpl(String displayName, Set tags, Optional> testClass, Optional testMethod) {
+ public TestInfoImpl(String displayName, Set tags, Optional> testClass, Optional testMethod) {
this.displayName = displayName;
this.tags = tags;
this.testClass = testClass;
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 9951f96734d44a..00000000000000
--- 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