HHH-18284 Test StaticClassLists more extensively
This commit is contained in:
parent
e9772c5c02
commit
a1962fd629
|
@ -15,4 +15,5 @@ dependencies {
|
|||
compileOnly "org.graalvm.sdk:graal-sdk:22.2.0"
|
||||
|
||||
testImplementation project( ':hibernate-core' )
|
||||
testImplementation libs.jandex
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.graalvm.internal;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
class CodeSource implements Closeable {
|
||||
|
||||
public static CodeSource open(URI location) throws IOException {
|
||||
if ( "jar".equals( location.getScheme() ) ) {
|
||||
var fs = FileSystems.newFileSystem( location, Map.of() );
|
||||
return new CodeSource( fs, fs.getRootDirectories().iterator().next() );
|
||||
}
|
||||
else if ( "file".equals( location.getScheme() ) && location.getPath().endsWith( ".jar" ) ) {
|
||||
location = URI.create( "jar:" + location );
|
||||
var fs = FileSystems.newFileSystem( location, Map.of() );
|
||||
return new CodeSource( fs, fs.getRootDirectories().iterator().next() );
|
||||
}
|
||||
else if ( "file".equals( location.getScheme() ) ) {
|
||||
return new CodeSource( null, Paths.get( location ) );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException( "Unsupported URI: " + location );
|
||||
}
|
||||
}
|
||||
|
||||
private final FileSystem toClose;
|
||||
private final Path root;
|
||||
|
||||
private CodeSource(FileSystem toClose, Path root) {
|
||||
this.toClose = toClose;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if ( toClose != null ) {
|
||||
toClose.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Path getRoot() {
|
||||
return root;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright Red Hat Inc. and Hibernate Authors
|
||||
*/
|
||||
package org.hibernate.graalvm.internal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jboss.jandex.ClassInfo;
|
||||
import org.jboss.jandex.DotName;
|
||||
import org.jboss.jandex.Index;
|
||||
import org.jboss.jandex.Indexer;
|
||||
|
||||
public final class JandexTestUtils {
|
||||
|
||||
private JandexTestUtils() {
|
||||
}
|
||||
|
||||
public static Index indexJar(Class<?> clazz) {
|
||||
return indexClasses( determineJarLocation( clazz ) );
|
||||
}
|
||||
|
||||
private static URI determineJarLocation(Class<?> clazz) {
|
||||
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
|
||||
try {
|
||||
return url.toURI();
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new IllegalStateException( "Cannot retrieve URI for JAR of " + clazz + "?", e );
|
||||
}
|
||||
}
|
||||
|
||||
private static Index indexClasses(URI classesUri) {
|
||||
try ( CodeSource cs = CodeSource.open( classesUri ) ) {
|
||||
Indexer indexer = new Indexer();
|
||||
try ( Stream<Path> stream = Files.walk( cs.getRoot() ) ) {
|
||||
for ( Iterator<Path> it = stream.iterator(); it.hasNext(); ) {
|
||||
Path path = it.next();
|
||||
if ( path.getFileName() == null || !path.getFileName().toString().endsWith( ".class" ) ) {
|
||||
continue;
|
||||
}
|
||||
try ( InputStream inputStream = Files.newInputStream( path ) ) {
|
||||
indexer.index( inputStream );
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexer.complete();
|
||||
}
|
||||
catch (RuntimeException | IOException e) {
|
||||
throw new IllegalStateException( "Cannot index classes at " + classesUri, e );
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> load(DotName className) {
|
||||
try {
|
||||
return Class.forName( className.toString() );
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException( "Could not load class " + className, e );
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Class<?>> findConcreteNamedImplementors(Index index, Class<?>... interfaces) {
|
||||
return Arrays.stream( interfaces ).map( DotName::createSimple )
|
||||
.flatMap( n -> findConcreteNamedImplementors( index, n ).stream() )
|
||||
.collect( Collectors.toSet() );
|
||||
}
|
||||
|
||||
private static Set<Class<?>> findConcreteNamedImplementors(Index index, DotName interfaceDotName) {
|
||||
assertThat( index.getClassByName( interfaceDotName ) ).isNotNull();
|
||||
return index.getAllKnownImplementors( interfaceDotName ).stream()
|
||||
.filter( c -> !c.isInterface()
|
||||
// Ignore anonymous classes
|
||||
&& c.simpleName() != null )
|
||||
.map( ClassInfo::name )
|
||||
.map( JandexTestUtils::load )
|
||||
.filter( c -> ( c.getModifiers() & Modifier.ABSTRACT ) == 0 )
|
||||
.collect( Collectors.toSet() );
|
||||
}
|
||||
|
||||
}
|
|
@ -7,33 +7,185 @@
|
|||
package org.hibernate.graalvm.internal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.graalvm.internal.JandexTestUtils.findConcreteNamedImplementors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.jboss.jandex.Index;
|
||||
|
||||
public class StaticClassListsTest {
|
||||
private static Index hibernateIndex;
|
||||
|
||||
@BeforeAll
|
||||
public static void index() throws IOException {
|
||||
hibernateIndex = JandexTestUtils.indexJar( Session.class );
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TypesNeedingAllConstructorsAccessible {
|
||||
@ParameterizedTest
|
||||
@EnumSource(TypesNeedingAllConstructorsAccessible_Category.class)
|
||||
void containsAllExpectedClasses(TypesNeedingAllConstructorsAccessible_Category category) {
|
||||
assertThat( StaticClassLists.typesNeedingAllConstructorsAccessible() )
|
||||
.containsAll( category.classes().collect( Collectors.toSet() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void meta_noMissingTestCategory() {
|
||||
assertThat( Arrays.stream( TypesNeedingAllConstructorsAccessible_Category.values() ).flatMap( TypesNeedingAllConstructorsAccessible_Category::classes ) )
|
||||
.as( "If this fails, a category is missing in " + TypesNeedingAllConstructorsAccessible_Category.class )
|
||||
.contains( StaticClassLists.typesNeedingAllConstructorsAccessible() );
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ORM 7: Move this inside TypesNeedingAllConstructorsAccessible (requires JDK 17) and rename to simple Category
|
||||
enum TypesNeedingAllConstructorsAccessible_Category {
|
||||
PERSISTERS {
|
||||
@Override
|
||||
Stream<Class<?>> classes() {
|
||||
return findConcreteNamedImplementors(
|
||||
hibernateIndex, EntityPersister.class, CollectionPersister.class )
|
||||
.stream();
|
||||
}
|
||||
},
|
||||
MISC {
|
||||
@Override
|
||||
Stream<Class<?>> classes() {
|
||||
// NOTE: Please avoid putting anything here, it's really a last resort.
|
||||
// Ideally you'd rather add new categories with their own way of listing classes,
|
||||
// like in PERSISTERS.
|
||||
// Putting anything here is running the risk of forgetting
|
||||
// why it was necessary in the first place...
|
||||
return Stream.of(
|
||||
// Logging - sometimes looked up without a static field
|
||||
org.hibernate.internal.CoreMessageLogger_$logger.class
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
abstract Stream<Class<?>> classes();
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TypesNeedingDefaultConstructorAccessible {
|
||||
@ParameterizedTest
|
||||
@EnumSource(TypesNeedingDefaultConstructorAccessible_Category.class)
|
||||
void containsAllExpectedClasses(TypesNeedingDefaultConstructorAccessible_Category category) {
|
||||
assertThat( StaticClassLists.typesNeedingDefaultConstructorAccessible() )
|
||||
.containsAll( category.classes().collect( Collectors.toSet() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void meta_noMissingTestCategory() {
|
||||
assertThat( Arrays.stream( TypesNeedingDefaultConstructorAccessible_Category.values() ).flatMap( TypesNeedingDefaultConstructorAccessible_Category::classes ) )
|
||||
.as( "If this fails, a category is missing in " + TypesNeedingDefaultConstructorAccessible_Category.class )
|
||||
.contains( StaticClassLists.typesNeedingDefaultConstructorAccessible() );
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ORM 7: Move this inside TypesNeedingDefaultConstructorAccessible (requires JDK 17) and rename to simple Category
|
||||
enum TypesNeedingDefaultConstructorAccessible_Category {
|
||||
MISC {
|
||||
@Override
|
||||
Stream<Class<?>> classes() {
|
||||
// NOTE: Please avoid putting anything here, it's really a last resort.
|
||||
// Ideally you'd rather add new categories with their own way of listing classes,
|
||||
// like in PERSISTERS.
|
||||
// Putting anything here is running the risk of forgetting
|
||||
// why it was necessary in the first place...
|
||||
return Stream.of(
|
||||
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl.class,
|
||||
org.hibernate.id.enhanced.SequenceStyleGenerator.class,
|
||||
org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl.class,
|
||||
org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl.class,
|
||||
org.hibernate.type.EnumType.class,
|
||||
org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor.class
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
abstract Stream<Class<?>> classes();
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TypesNeedingArrayCopy {
|
||||
@Test
|
||||
void containsEventListenerInterfaces() {
|
||||
@ParameterizedTest
|
||||
@EnumSource(TypesNeedingArrayCopy_Category.class)
|
||||
void containsAllExpectedClasses(TypesNeedingArrayCopy_Category category) {
|
||||
assertThat( StaticClassLists.typesNeedingArrayCopy() )
|
||||
.containsAll( eventListenerInterfaces().collect( Collectors.toSet() ) );
|
||||
.containsAll( category.classes().collect( Collectors.toSet() ) );
|
||||
}
|
||||
|
||||
static Stream<Class<?>> eventListenerInterfaces() {
|
||||
@Test
|
||||
void meta_noMissingTestCategory() {
|
||||
assertThat( Arrays.stream( TypesNeedingArrayCopy_Category.values() ).flatMap( TypesNeedingArrayCopy_Category::classes ) )
|
||||
.as( "If this fails, a category is missing in " + TypesNeedingArrayCopy_Category.class )
|
||||
.contains( StaticClassLists.typesNeedingArrayCopy() );
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ORM 7: Move this inside TypesNeedingArrayCopy (requires JDK 17) and rename to simple Category
|
||||
enum TypesNeedingArrayCopy_Category {
|
||||
EVENT_LISTENER_INTERFACES {
|
||||
@Override
|
||||
Stream<Class<?>> classes() {
|
||||
return EventType.values().stream().map( EventType::baseListenerInterface )
|
||||
.map( c -> Array.newInstance( c, 0 ).getClass() );
|
||||
}
|
||||
},
|
||||
MISC {
|
||||
@Override
|
||||
Stream<Class<?>> classes() {
|
||||
// NOTE: Please avoid putting anything here, it's really a last resort.
|
||||
// Ideally you'd rather add new categories with their own way of listing classes,
|
||||
// like in EVENT_LISTENER_INTERFACES.
|
||||
// Putting anything here is running the risk of forgetting
|
||||
// why it was necessary in the first place...
|
||||
return Stream.of(
|
||||
// Java classes -- the why is lost to history
|
||||
java.util.function.Function[].class,
|
||||
java.util.List[].class,
|
||||
java.util.Map.Entry[].class,
|
||||
java.util.function.Supplier[].class,
|
||||
// Graphs -- the why is lost to history
|
||||
org.hibernate.graph.spi.AttributeNodeImplementor[].class,
|
||||
org.hibernate.sql.results.graph.FetchParent[].class,
|
||||
org.hibernate.graph.spi.GraphImplementor[].class,
|
||||
org.hibernate.graph.internal.parse.SubGraphGenerator[].class,
|
||||
// AST/parsing -- no way to detect this automatically, you just have to know.
|
||||
org.hibernate.sql.ast.Clause[].class,
|
||||
org.hibernate.query.hql.spi.DotIdentifierConsumer[].class,
|
||||
org.hibernate.query.sqm.sql.FromClauseIndex[].class,
|
||||
org.hibernate.query.sqm.spi.ParameterDeclarationContext[].class,
|
||||
org.hibernate.sql.ast.tree.select.QueryPart[].class,
|
||||
org.hibernate.sql.ast.spi.SqlAstProcessingState[].class,
|
||||
org.hibernate.query.hql.spi.SqmCreationProcessingState[].class,
|
||||
org.hibernate.sql.ast.tree.Statement[].class,
|
||||
// Various internals -- the why is lost to history
|
||||
org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState[].class
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
abstract Stream<Class<?>> classes();
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
Loading…
Reference in New Issue