Skip to content

Commit a1962fd

Browse files
yrodiereSanne
authored andcommitted
HHH-18284 Test StaticClassLists more extensively
1 parent e9772c5 commit a1962fd

File tree

4 files changed

+310
-6
lines changed

4 files changed

+310
-6
lines changed

hibernate-graalvm/hibernate-graalvm.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ dependencies {
1515
compileOnly "org.graalvm.sdk:graal-sdk:22.2.0"
1616

1717
testImplementation project( ':hibernate-core' )
18+
testImplementation libs.jandex
1819
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.graalvm.internal;
8+
9+
import java.io.Closeable;
10+
import java.io.IOException;
11+
import java.net.URI;
12+
import java.nio.file.FileSystem;
13+
import java.nio.file.FileSystems;
14+
import java.nio.file.Path;
15+
import java.nio.file.Paths;
16+
import java.util.Map;
17+
18+
class CodeSource implements Closeable {
19+
20+
public static CodeSource open(URI location) throws IOException {
21+
if ( "jar".equals( location.getScheme() ) ) {
22+
var fs = FileSystems.newFileSystem( location, Map.of() );
23+
return new CodeSource( fs, fs.getRootDirectories().iterator().next() );
24+
}
25+
else if ( "file".equals( location.getScheme() ) && location.getPath().endsWith( ".jar" ) ) {
26+
location = URI.create( "jar:" + location );
27+
var fs = FileSystems.newFileSystem( location, Map.of() );
28+
return new CodeSource( fs, fs.getRootDirectories().iterator().next() );
29+
}
30+
else if ( "file".equals( location.getScheme() ) ) {
31+
return new CodeSource( null, Paths.get( location ) );
32+
}
33+
else {
34+
throw new IllegalArgumentException( "Unsupported URI: " + location );
35+
}
36+
}
37+
38+
private final FileSystem toClose;
39+
private final Path root;
40+
41+
private CodeSource(FileSystem toClose, Path root) {
42+
this.toClose = toClose;
43+
this.root = root;
44+
}
45+
46+
@Override
47+
public void close() throws IOException {
48+
if ( toClose != null ) {
49+
toClose.close();
50+
}
51+
}
52+
53+
public Path getRoot() {
54+
return root;
55+
}
56+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.graalvm.internal;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.lang.reflect.Modifier;
12+
import java.net.URI;
13+
import java.net.URISyntaxException;
14+
import java.net.URL;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.util.Arrays;
18+
import java.util.Iterator;
19+
import java.util.Set;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.Stream;
22+
23+
import org.jboss.jandex.ClassInfo;
24+
import org.jboss.jandex.DotName;
25+
import org.jboss.jandex.Index;
26+
import org.jboss.jandex.Indexer;
27+
28+
public final class JandexTestUtils {
29+
30+
private JandexTestUtils() {
31+
}
32+
33+
public static Index indexJar(Class<?> clazz) {
34+
return indexClasses( determineJarLocation( clazz ) );
35+
}
36+
37+
private static URI determineJarLocation(Class<?> clazz) {
38+
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
39+
try {
40+
return url.toURI();
41+
}
42+
catch (URISyntaxException e) {
43+
throw new IllegalStateException( "Cannot retrieve URI for JAR of " + clazz + "?", e );
44+
}
45+
}
46+
47+
private static Index indexClasses(URI classesUri) {
48+
try ( CodeSource cs = CodeSource.open( classesUri ) ) {
49+
Indexer indexer = new Indexer();
50+
try ( Stream<Path> stream = Files.walk( cs.getRoot() ) ) {
51+
for ( Iterator<Path> it = stream.iterator(); it.hasNext(); ) {
52+
Path path = it.next();
53+
if ( path.getFileName() == null || !path.getFileName().toString().endsWith( ".class" ) ) {
54+
continue;
55+
}
56+
try ( InputStream inputStream = Files.newInputStream( path ) ) {
57+
indexer.index( inputStream );
58+
}
59+
}
60+
}
61+
return indexer.complete();
62+
}
63+
catch (RuntimeException | IOException e) {
64+
throw new IllegalStateException( "Cannot index classes at " + classesUri, e );
65+
}
66+
}
67+
68+
public static Class<?> load(DotName className) {
69+
try {
70+
return Class.forName( className.toString() );
71+
}
72+
catch (ClassNotFoundException e) {
73+
throw new RuntimeException( "Could not load class " + className, e );
74+
}
75+
}
76+
77+
public static Set<Class<?>> findConcreteNamedImplementors(Index index, Class<?>... interfaces) {
78+
return Arrays.stream( interfaces ).map( DotName::createSimple )
79+
.flatMap( n -> findConcreteNamedImplementors( index, n ).stream() )
80+
.collect( Collectors.toSet() );
81+
}
82+
83+
private static Set<Class<?>> findConcreteNamedImplementors(Index index, DotName interfaceDotName) {
84+
assertThat( index.getClassByName( interfaceDotName ) ).isNotNull();
85+
return index.getAllKnownImplementors( interfaceDotName ).stream()
86+
.filter( c -> !c.isInterface()
87+
// Ignore anonymous classes
88+
&& c.simpleName() != null )
89+
.map( ClassInfo::name )
90+
.map( JandexTestUtils::load )
91+
.filter( c -> ( c.getModifiers() & Modifier.ABSTRACT ) == 0 )
92+
.collect( Collectors.toSet() );
93+
}
94+
95+
}

hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/StaticClassListsTest.java

Lines changed: 158 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,187 @@
77
package org.hibernate.graalvm.internal;
88

99
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.hibernate.graalvm.internal.JandexTestUtils.findConcreteNamedImplementors;
1011

12+
import java.io.IOException;
1113
import java.lang.reflect.Array;
1214
import java.lang.reflect.Constructor;
15+
import java.util.Arrays;
1316
import java.util.stream.Collectors;
1417
import java.util.stream.Stream;
1518

19+
import org.hibernate.Session;
1620
import org.hibernate.event.spi.EventType;
1721
import org.hibernate.internal.util.ReflectHelper;
22+
import org.hibernate.persister.collection.CollectionPersister;
23+
import org.hibernate.persister.entity.EntityPersister;
1824

1925
import org.junit.Assert;
26+
import org.junit.jupiter.api.BeforeAll;
2027
import org.junit.jupiter.api.Nested;
2128
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.EnumSource;
31+
32+
import org.jboss.jandex.Index;
2233

2334
public class StaticClassListsTest {
35+
private static Index hibernateIndex;
36+
37+
@BeforeAll
38+
public static void index() throws IOException {
39+
hibernateIndex = JandexTestUtils.indexJar( Session.class );
40+
}
2441

2542
@Nested
26-
class TypesNeedingArrayCopy {
43+
class TypesNeedingAllConstructorsAccessible {
44+
@ParameterizedTest
45+
@EnumSource(TypesNeedingAllConstructorsAccessible_Category.class)
46+
void containsAllExpectedClasses(TypesNeedingAllConstructorsAccessible_Category category) {
47+
assertThat( StaticClassLists.typesNeedingAllConstructorsAccessible() )
48+
.containsAll( category.classes().collect( Collectors.toSet() ) );
49+
}
50+
2751
@Test
28-
void containsEventListenerInterfaces() {
52+
void meta_noMissingTestCategory() {
53+
assertThat( Arrays.stream( TypesNeedingAllConstructorsAccessible_Category.values() ).flatMap( TypesNeedingAllConstructorsAccessible_Category::classes ) )
54+
.as( "If this fails, a category is missing in " + TypesNeedingAllConstructorsAccessible_Category.class )
55+
.contains( StaticClassLists.typesNeedingAllConstructorsAccessible() );
56+
}
57+
}
58+
59+
// TODO ORM 7: Move this inside TypesNeedingAllConstructorsAccessible (requires JDK 17) and rename to simple Category
60+
enum TypesNeedingAllConstructorsAccessible_Category {
61+
PERSISTERS {
62+
@Override
63+
Stream<Class<?>> classes() {
64+
return findConcreteNamedImplementors(
65+
hibernateIndex, EntityPersister.class, CollectionPersister.class )
66+
.stream();
67+
}
68+
},
69+
MISC {
70+
@Override
71+
Stream<Class<?>> classes() {
72+
// NOTE: Please avoid putting anything here, it's really a last resort.
73+
// Ideally you'd rather add new categories with their own way of listing classes,
74+
// like in PERSISTERS.
75+
// Putting anything here is running the risk of forgetting
76+
// why it was necessary in the first place...
77+
return Stream.of(
78+
// Logging - sometimes looked up without a static field
79+
org.hibernate.internal.CoreMessageLogger_$logger.class
80+
);
81+
}
82+
};
83+
84+
abstract Stream<Class<?>> classes();
85+
}
86+
87+
@Nested
88+
class TypesNeedingDefaultConstructorAccessible {
89+
@ParameterizedTest
90+
@EnumSource(TypesNeedingDefaultConstructorAccessible_Category.class)
91+
void containsAllExpectedClasses(TypesNeedingDefaultConstructorAccessible_Category category) {
92+
assertThat( StaticClassLists.typesNeedingDefaultConstructorAccessible() )
93+
.containsAll( category.classes().collect( Collectors.toSet() ) );
94+
}
95+
96+
@Test
97+
void meta_noMissingTestCategory() {
98+
assertThat( Arrays.stream( TypesNeedingDefaultConstructorAccessible_Category.values() ).flatMap( TypesNeedingDefaultConstructorAccessible_Category::classes ) )
99+
.as( "If this fails, a category is missing in " + TypesNeedingDefaultConstructorAccessible_Category.class )
100+
.contains( StaticClassLists.typesNeedingDefaultConstructorAccessible() );
101+
}
102+
}
103+
104+
// TODO ORM 7: Move this inside TypesNeedingDefaultConstructorAccessible (requires JDK 17) and rename to simple Category
105+
enum TypesNeedingDefaultConstructorAccessible_Category {
106+
MISC {
107+
@Override
108+
Stream<Class<?>> classes() {
109+
// NOTE: Please avoid putting anything here, it's really a last resort.
110+
// Ideally you'd rather add new categories with their own way of listing classes,
111+
// like in PERSISTERS.
112+
// Putting anything here is running the risk of forgetting
113+
// why it was necessary in the first place...
114+
return Stream.of(
115+
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl.class,
116+
org.hibernate.id.enhanced.SequenceStyleGenerator.class,
117+
org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl.class,
118+
org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl.class,
119+
org.hibernate.type.EnumType.class,
120+
org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor.class
121+
);
122+
}
123+
};
124+
125+
abstract Stream<Class<?>> classes();
126+
}
127+
128+
@Nested
129+
class TypesNeedingArrayCopy {
130+
@ParameterizedTest
131+
@EnumSource(TypesNeedingArrayCopy_Category.class)
132+
void containsAllExpectedClasses(TypesNeedingArrayCopy_Category category) {
29133
assertThat( StaticClassLists.typesNeedingArrayCopy() )
30-
.containsAll( eventListenerInterfaces().collect( Collectors.toSet() ) );
134+
.containsAll( category.classes().collect( Collectors.toSet() ) );
31135
}
32136

33-
static Stream<Class<?>> eventListenerInterfaces() {
34-
return EventType.values().stream().map( EventType::baseListenerInterface )
35-
.map( c -> Array.newInstance( c, 0 ).getClass() );
137+
@Test
138+
void meta_noMissingTestCategory() {
139+
assertThat( Arrays.stream( TypesNeedingArrayCopy_Category.values() ).flatMap( TypesNeedingArrayCopy_Category::classes ) )
140+
.as( "If this fails, a category is missing in " + TypesNeedingArrayCopy_Category.class )
141+
.contains( StaticClassLists.typesNeedingArrayCopy() );
36142
}
37143
}
38144

145+
// TODO ORM 7: Move this inside TypesNeedingArrayCopy (requires JDK 17) and rename to simple Category
146+
enum TypesNeedingArrayCopy_Category {
147+
EVENT_LISTENER_INTERFACES {
148+
@Override
149+
Stream<Class<?>> classes() {
150+
return EventType.values().stream().map( EventType::baseListenerInterface )
151+
.map( c -> Array.newInstance( c, 0 ).getClass() );
152+
}
153+
},
154+
MISC {
155+
@Override
156+
Stream<Class<?>> classes() {
157+
// NOTE: Please avoid putting anything here, it's really a last resort.
158+
// Ideally you'd rather add new categories with their own way of listing classes,
159+
// like in EVENT_LISTENER_INTERFACES.
160+
// Putting anything here is running the risk of forgetting
161+
// why it was necessary in the first place...
162+
return Stream.of(
163+
// Java classes -- the why is lost to history
164+
java.util.function.Function[].class,
165+
java.util.List[].class,
166+
java.util.Map.Entry[].class,
167+
java.util.function.Supplier[].class,
168+
// Graphs -- the why is lost to history
169+
org.hibernate.graph.spi.AttributeNodeImplementor[].class,
170+
org.hibernate.sql.results.graph.FetchParent[].class,
171+
org.hibernate.graph.spi.GraphImplementor[].class,
172+
org.hibernate.graph.internal.parse.SubGraphGenerator[].class,
173+
// AST/parsing -- no way to detect this automatically, you just have to know.
174+
org.hibernate.sql.ast.Clause[].class,
175+
org.hibernate.query.hql.spi.DotIdentifierConsumer[].class,
176+
org.hibernate.query.sqm.sql.FromClauseIndex[].class,
177+
org.hibernate.query.sqm.spi.ParameterDeclarationContext[].class,
178+
org.hibernate.sql.ast.tree.select.QueryPart[].class,
179+
org.hibernate.sql.ast.spi.SqlAstProcessingState[].class,
180+
org.hibernate.query.hql.spi.SqmCreationProcessingState[].class,
181+
org.hibernate.sql.ast.tree.Statement[].class,
182+
// Various internals -- the why is lost to history
183+
org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState[].class
184+
);
185+
}
186+
};
187+
188+
abstract Stream<Class<?>> classes();
189+
}
190+
39191
@Nested
40192
class BasicConstructorsAvailable {
41193

0 commit comments

Comments
 (0)