Skip to content

Ensure compatibility with JUnit 5.12+ #10576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/java-module.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies {

testImplementation testLibs.byteman

testRuntimeOnly testLibs.junit5Launcher
testRuntimeOnly testLibs.log4j2
testRuntimeOnly libs.byteBuddy

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Blob;
Expand All @@ -40,9 +41,9 @@ protected Class<?>[] getAnnotatedClasses() {

@Test
@Priority(10)
public void testGenerateProxyNoStream() {
public void testGenerateProxyNoStream() throws URISyntaxException {
final Path path = Path.of( Thread.currentThread().getContextClassLoader()
.getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).getPath() );
.getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).toURI() );
doInJPA( this::entityManagerFactory, entityManager -> {
final Asset asset = new Asset();
asset.setFileName( "blob.txt" );
Expand Down Expand Up @@ -84,9 +85,9 @@ public void testGenerateProxyNoStream() {
comment = "The driver closes the stream, so it cannot be reused by envers")
@SkipForDialect(value = SybaseDialect.class,
comment = "The driver closes the stream, so it cannot be reused by envers")
public void testGenerateProxyStream() {
public void testGenerateProxyStream() throws URISyntaxException {
final Path path = Path.of( Thread.currentThread().getContextClassLoader()
.getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).getPath() );
.getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).toURI() );

try (final InputStream stream = new BufferedInputStream( Files.newInputStream( path ) )) {
doInJPA( this::entityManagerFactory, entityManager -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,23 @@
import static org.hibernate.testing.bytecode.enhancement.extension.engine.BytecodeEnhancedClassUtils.enhanceTestClass;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDirFactory;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.engine.JupiterTestEngine;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor;
Expand All @@ -44,6 +37,7 @@
import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.EngineExecutionListener;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
Expand Down Expand Up @@ -143,9 +137,13 @@ private void replaceWithEnhanced(Class<?> enhanced, ClassBasedTestDescriptor des
Set<? extends TestDescriptor> children, TestDescriptor parent, String[] testEnhancedClasses,
Object enhancementContextId)
throws NoSuchMethodException {
DelegatingJupiterConfiguration configuration = new DelegatingJupiterConfiguration( jc, enhancementContextId );
final JupiterConfiguration configuration = (JupiterConfiguration) Proxy.newProxyInstance(
BytecodeEnhancedTestEngine.class.getClassLoader(),
new Class[] { JupiterConfiguration.class },
new JupiterConfigurationInvocationHandler( jc, enhancementContextId )
);

ClassTestDescriptor updated = new ClassTestDescriptor(
final ClassTestDescriptor updated = new ClassTestDescriptor(
convertUniqueId( descriptor.getUniqueId(), enhancementContextId ),
enhanced,
configuration
Expand Down Expand Up @@ -210,6 +208,24 @@ private Method findMethodReplacement(ClassTestDescriptor updated, Method testMet

@Override
protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) {
try {
// Try constructing the JupiterEngineExecutionContext the way it is done in 5.13+
final Class<?> storeFacadeClass =
Class.forName( "org.junit.jupiter.engine.descriptor.LauncherStoreFacade" );
final Method getStore = ExecutionRequest.class.getMethod( "getStore" );
final Constructor<?> storeConstructor = storeFacadeClass.getConstructor( getStore.getReturnType() );
final Constructor<JupiterEngineExecutionContext> constructor = JupiterEngineExecutionContext.class
.getConstructor( EngineExecutionListener.class, JupiterConfiguration.class, storeFacadeClass );
return constructor.newInstance(
request.getEngineExecutionListener(),
this.getJupiterConfiguration( request ),
storeConstructor.newInstance( getStore.invoke( request ) )
);
}
catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
// Ignore errors as they are probably due to version mismatches and try the 5.12 way
}

return new JupiterEngineExecutionContext(
request.getEngineExecutionListener(),
this.getJupiterConfiguration( request )
Expand Down Expand Up @@ -237,111 +253,51 @@ public Context(ExecutionRequest request) {
}
}

private static class DelegatingJupiterConfiguration implements JupiterConfiguration {
private static class JupiterConfigurationInvocationHandler implements InvocationHandler {
private final JupiterConfiguration configuration;
private final DelegatingDisplayNameGenerator displayNameGenerator;
private final DisplayNameGenerator displayNameGenerator;

private DelegatingJupiterConfiguration(JupiterConfiguration configuration, Object id) {
private JupiterConfigurationInvocationHandler(JupiterConfiguration configuration, Object id) {
this.configuration = configuration;
displayNameGenerator = new DelegatingDisplayNameGenerator(
configuration.getDefaultDisplayNameGenerator(),
id
displayNameGenerator = (DisplayNameGenerator) Proxy.newProxyInstance(
BytecodeEnhancedTestEngine.class.getClassLoader(),
new Class[]{ DisplayNameGenerator.class },
new DisplayNameGeneratorInvocationHandler( configuration.getDefaultDisplayNameGenerator(), id )
);
}

@Override
public Optional<String> getRawConfigurationParameter(String s) {
return configuration.getRawConfigurationParameter( s );
}

@Override
public <T> Optional<T> getRawConfigurationParameter(String s, Function<String, T> function) {
return configuration.getRawConfigurationParameter( s, function );
}

@Override
public boolean isParallelExecutionEnabled() {
return configuration.isParallelExecutionEnabled();
}

@Override
public boolean isExtensionAutoDetectionEnabled() {
return configuration.isExtensionAutoDetectionEnabled();
}

@Override
public ExecutionMode getDefaultExecutionMode() {
return configuration.getDefaultExecutionMode();
}

@Override
public ExecutionMode getDefaultClassesExecutionMode() {
return configuration.getDefaultClassesExecutionMode();
}

@Override
public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() {
return configuration.getDefaultTestInstanceLifecycle();
}

@Override
public Predicate<ExecutionCondition> getExecutionConditionFilter() {
return configuration.getExecutionConditionFilter();
}

@Override
public DisplayNameGenerator getDefaultDisplayNameGenerator() {
return displayNameGenerator;
}

@Override
public Optional<MethodOrderer> getDefaultTestMethodOrderer() {
return configuration.getDefaultTestMethodOrderer();
}

@Override
public Optional<ClassOrderer> getDefaultTestClassOrderer() {
return configuration.getDefaultTestClassOrderer();
}

@Override
public CleanupMode getDefaultTempDirCleanupMode() {
return configuration.getDefaultTempDirCleanupMode();
}

@Override
public Supplier<TempDirFactory> getDefaultTempDirFactorySupplier() {
return configuration.getDefaultTempDirFactorySupplier();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ( "getDefaultDisplayNameGenerator".equals( method.getName() ) ) {
return displayNameGenerator;
}
else {
return method.invoke( configuration, args );
}
}
}

private static class DelegatingDisplayNameGenerator implements DisplayNameGenerator {
private static class DisplayNameGeneratorInvocationHandler implements InvocationHandler {

private final DisplayNameGenerator delegate;
private final Object id;

private DelegatingDisplayNameGenerator(DisplayNameGenerator delegate, Object id) {
private DisplayNameGeneratorInvocationHandler(DisplayNameGenerator delegate, Object id) {
this.delegate = delegate;
this.id = id;
}

@Override
public String generateDisplayNameForClass(Class<?> aClass) {
return prefix() + delegate.generateDisplayNameForClass( aClass );
}

private String prefix() {
return "Enhanced" + ( id == null ? "" : "[" + id + "]" ) + ":";
}

@Override
public String generateDisplayNameForNestedClass(Class<?> aClass) {
return prefix() + delegate.generateDisplayNameForNestedClass( aClass );
}

@Override
public String generateDisplayNameForMethod(Class<?> aClass, Method method) {
return prefix() + delegate.generateDisplayNameForMethod( aClass, method );
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Object result = method.invoke( delegate, args );
if ( method.getDeclaringClass() == DisplayNameGenerator.class ) {
return prefix() + result;
}
return result;
}
}

Expand All @@ -350,9 +306,7 @@ private static class EnhancementWorkedCheckMethodTestDescriptor extends TestMeth
private final boolean enhanced;
private final String[] classes;

public EnhancementWorkedCheckMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass,
JupiterConfiguration configuration,
boolean enhanced, String[] classes) {
public EnhancementWorkedCheckMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, JupiterConfiguration configuration, boolean enhanced, String[] classes) {
super(
prepareId( uniqueId, testMethod( enhanced ) ),
testClass, testMethod( enhanced ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
testImplementation testLibs.junit5Api

testRuntimeOnly testLibs.junit5Engine
testRuntimeOnly testLibs.junit5Launcher
}

gradlePlugin {
Expand Down
Loading