HHH-15344 - Ability to apply testing annotations at method-level

- `@DomainModel`
This commit is contained in:
Steve Ebersole 2022-06-16 03:24:52 -05:00
parent 6c461a3674
commit 763d1764cd
21 changed files with 248 additions and 79 deletions

View File

@ -18,7 +18,7 @@ import org.hibernate.integrator.spi.Integrator;
* Used to define the bootstrap ServiceRegistry to be used for testing.
*/
@Inherited
@Target( ElementType.TYPE )
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ServiceRegistryFunctionalTesting

View File

@ -12,8 +12,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.persistence.SharedCacheMode;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.cache.spi.access.AccessType;
@ -22,6 +20,8 @@ import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import jakarta.persistence.SharedCacheMode;
/**
* @asciidoc
*
@ -81,7 +81,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* @author Steve Ebersole
*/
@Inherited
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@TestInstance( TestInstance.Lifecycle.PER_CLASS )

View File

@ -26,7 +26,7 @@ import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.testing.orm.domain.DomainModelDescriptor;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
@ -44,22 +44,67 @@ import jakarta.persistence.SharedCacheMode;
* @author Steve Ebersole
*/
public class DomainModelExtension
implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler {
implements TestInstancePostProcessor, BeforeEachCallback, TestExecutionExceptionHandler {
private static final String MODEL_KEY = MetadataImplementor.class.getName();
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
assert context.getTestClass().isPresent();
final Optional<DomainModel> domainModelAnnRef = AnnotationSupport.findAnnotation(
context.getElement().get(),
DomainModel.class
);
if ( domainModelAnnRef.isPresent()
|| DomainModelProducer.class.isAssignableFrom( context.getRequiredTestClass() ) ) {
final DomainModelScope created = createDomainModelScope( testInstance, domainModelAnnRef, context );
locateExtensionStore( testInstance, context ).put( MODEL_KEY, created );
}
}
@Override
public void beforeEach(ExtensionContext context) {
assert context.getTestMethod().isPresent();
assert context.getRequiredTestMethod() == context.getElement().get();
assert context.getTestInstance().isPresent();
final Optional<DomainModel> domainModelAnnRef = AnnotationSupport.findAnnotation(
context.getElement().get(),
DomainModel.class
);
if ( domainModelAnnRef.isEmpty() ) {
// assume the annotations are defined on the class-level...
// will be validated by the parameter-resolver or SFS-extension
return;
}
final DomainModelScope created = createDomainModelScope( context.getRequiredTestInstance(), domainModelAnnRef, context );
final ExtensionContext.Store extensionStore = locateExtensionStore( context.getRequiredTestInstance(), context );
extensionStore.put( MODEL_KEY, created );
}
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context );
final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.get( MODEL_KEY );
if ( scope != null ) {
scope.releaseModel();
}
throw throwable;
}
private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) {
return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance );
}
public static DomainModelScope findDomainModelScope(Object testInstance, ExtensionContext context) {
final ExtensionContext.Store store = locateExtensionStore( testInstance, context );
final DomainModelScope existing = (DomainModelScope) store.get( MODEL_KEY );
if ( existing != null ) {
return existing;
}
private static DomainModelScope createDomainModelScope(
Object testInstance, Optional<DomainModel> domainModelAnnRef,
ExtensionContext context) {
final ServiceRegistryScope serviceRegistryScope = ServiceRegistryExtension.findServiceRegistryScope(
testInstance,
context
@ -71,21 +116,14 @@ public class DomainModelExtension
modelProducer = (DomainModelProducer) testInstance;
}
else {
assert domainModelAnnRef != null && domainModelAnnRef.isPresent();
modelProducer = serviceRegistry -> {
if ( !context.getElement().isPresent() ) {
if ( context.getElement().isEmpty() ) {
throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() );
}
final Optional<DomainModel> domainModelAnnotationWrapper = AnnotationSupport.findAnnotation(
context.getElement().get(),
DomainModel.class
);
if ( !domainModelAnnotationWrapper.isPresent() ) {
throw new RuntimeException( "Could not locate @DomainModel annotation : " + context.getDisplayName() );
}
final DomainModel domainModelAnnotation = domainModelAnnotationWrapper.get();
final DomainModel domainModelAnnotation = domainModelAnnRef.get();
final MetadataSources metadataSources = new MetadataSources( serviceRegistry );
final ManagedBeanRegistry managedBeanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
@ -108,7 +146,7 @@ public class DomainModelExtension
}
}
for ( Class annotatedClass : domainModelAnnotation.annotatedClasses() ) {
for ( Class<?> annotatedClass : domainModelAnnotation.annotatedClasses() ) {
metadataSources.addAnnotatedClass( annotatedClass );
}
@ -157,12 +195,34 @@ public class DomainModelExtension
( (DomainModelScopeAware) testInstance ).injectTestModelScope( scope );
}
locateExtensionStore( testInstance, context ).put( MODEL_KEY, scope );
return scope;
}
protected static final void applyCacheSettings(Metadata metadata, boolean overrideCacheStrategy, String cacheConcurrencyStrategy) {
public static DomainModelScope findDomainModelScope(Object testInstance, ExtensionContext context) {
// todo : allow for method-level
final ExtensionContext.Store store = locateExtensionStore( testInstance, context );
final DomainModelScope existing = (DomainModelScope) store.get( MODEL_KEY );
if ( existing != null ) {
return existing;
}
final Optional<DomainModel> domainModelAnnRef = AnnotationSupport.findAnnotation(
context.getElement().get(),
DomainModel.class
);
if ( domainModelAnnRef.isEmpty() ) {
throw new RuntimeException( "Could not locate @DomainModel annotation : " + context.getDisplayName() );
}
final DomainModelScope created = createDomainModelScope( testInstance, domainModelAnnRef, context );
locateExtensionStore( testInstance, context ).put( MODEL_KEY, created );
return created;
}
protected static void applyCacheSettings(Metadata metadata, boolean overrideCacheStrategy, String cacheConcurrencyStrategy) {
if ( !overrideCacheStrategy ) {
return;
}
@ -178,9 +238,9 @@ public class DomainModelExtension
boolean hasLob = false;
final Iterator props = entityBinding.getPropertyClosureIterator();
final Iterator<Property> props = entityBinding.getPropertyClosureIterator();
while ( props.hasNext() ) {
final Property prop = (Property) props.next();
final Property prop = props.next();
if ( prop.getValue().isSimpleValue() ) {
if ( isLob( (SimpleValue) prop.getValue() ) ) {
hasLob = true;
@ -226,33 +286,6 @@ public class DomainModelExtension
return false;
}
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
findDomainModelScope( testInstance, context );
}
@Override
public void afterAll(ExtensionContext context) {
final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context );
final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.remove( MODEL_KEY );
if ( scope != null ) {
scope.close();
}
}
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context );
final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.get( MODEL_KEY );
if ( scope != null ) {
scope.releaseModel();
}
throw throwable;
}
public static class DomainModelScopeImpl implements DomainModelScope, ExtensionContext.Store.CloseableResource {
private final ServiceRegistryScope serviceRegistryScope;
private final DomainModelProducer producer;

View File

@ -20,13 +20,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@TestInstance( TestInstance.Lifecycle.PER_CLASS )
//@ExtendWith( ServiceRegistryExtension.class )
//@ExtendWith( ServiceRegistryParameterResolver.class )
@ServiceRegistryFunctionalTesting
@ExtendWith( ExpectedExceptionExtension.class )

View File

@ -13,7 +13,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
/**
@ -24,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* @author Steve Ebersole
*/
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable( FailureExpectedGroup.class )

View File

@ -12,14 +12,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* @author Steve Ebersole
*/
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith( FailureExpectedExtension.class )

View File

@ -32,7 +32,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* @author Chris Cranford
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
@TestInstance(TestInstance.Lifecycle.PER_CLASS )
@ExtendWith(EntityManagerFactoryScopeExtension.class)

View File

@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* better option.
*/
@Inherited
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith( LoggingInspectionsExtension.class )

View File

@ -20,7 +20,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* For watching a multiple message-keys, see {@link LoggingInspections}
*/
@Inherited
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith( MessageKeyInspectionExtension.class )

View File

@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@Repeatable( RequiresDialects.class )
@ExtendWith( DialectFilterExtension.class )

View File

@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.TYPE, ElementType.METHOD})
@Repeatable( RequiresDialectFeatureGroup.class )
@ExtendWith( DialectFilterExtension.class )

View File

@ -22,7 +22,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.TYPE, ElementType.METHOD})
@ExtendWith( DialectFilterExtension.class )
public @interface RequiresDialectFeatureGroup {

View File

@ -19,7 +19,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Target({ ElementType.TYPE, ElementType.METHOD})
@ExtendWith( DialectFilterExtension.class )
public @interface RequiresDialects {

View File

@ -62,7 +62,7 @@ import org.hibernate.service.spi.ServiceContributor;
* @author Steve Ebersole
*/
@Inherited
@Target( ElementType.TYPE )
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention( RetentionPolicy.RUNTIME )
@ServiceRegistryFunctionalTesting

View File

@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@TestInstance( TestInstance.Lifecycle.PER_CLASS )

View File

@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
* @author Steve Ebersole
*/
@Inherited
@Target( ElementType.TYPE )
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention( RetentionPolicy.RUNTIME )
@TestInstance( TestInstance.Lifecycle.PER_CLASS )

View File

@ -33,7 +33,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@TestInstance( TestInstance.Lifecycle.PER_CLASS )

View File

@ -29,7 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
*/
@Inherited
@Retention( RetentionPolicy.RUNTIME )
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ElementType.TYPE, ElementType.METHOD})
@Repeatable( SkipForDialectGroup.class )
@ExtendWith( DialectFilterExtension.class )

View File

@ -0,0 +1,43 @@
/*
* 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.testing.annotations;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class AnotherEntity {
@Id
private Integer id;
@Basic
private String name;
private AnotherEntity() {
// for Hibernate use
}
public AnotherEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.testing.annotations.methods;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.testing.annotations.AnEntity;
import org.hibernate.testing.annotations.AnotherEntity;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@ServiceRegistry( settings = @Setting( name = "simple", value = "simple-value"))
@DomainModel( annotatedClasses = AnEntity.class )
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DomainModelTesting {
private int executingTestOrder;
@Test
@Order( 2 )
public void testClassLevelAnnotations(DomainModelScope scope) {
executingTestOrder = 2;
classLevelAssertions( scope );
}
private void classLevelAssertions(DomainModelScope scope) {
assertThat( scope.getDomainModel().getEntityBinding( AnEntity.class.getName() ) ).isNotNull();
assertThat( scope.getDomainModel().getEntityBinding( AnotherEntity.class.getName() ) ).isNull();
settingAssertions( scope );
}
private void settingAssertions(DomainModelScope scope) {
final org.hibernate.service.ServiceRegistry serviceRegistry = ( (MetadataImplementor) scope.getDomainModel() )
.getTypeConfiguration()
.getServiceRegistry();
final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
assertThat( configurationService.getSettings().get( "simple" ) ).isEqualTo( "simple-value" );
}
@Test
@DomainModel( annotatedClasses = AnotherEntity.class )
@Order( 1 )
public void testMethodLevelAnnotations(DomainModelScope scope) {
executingTestOrder = 1;
methodLevelAssertions( scope );
}
private void methodLevelAssertions(DomainModelScope scope) {
assertThat( scope.getDomainModel().getEntityBinding( AnEntity.class.getName() ) ).isNull();
assertThat( scope.getDomainModel().getEntityBinding( AnotherEntity.class.getName() ) ).isNotNull();
settingAssertions( scope );
}
@AfterEach
public void doStuffAfter(DomainModelScope scope) {
if ( executingTestOrder == 1 ) {
methodLevelAssertions( scope );
}
else if ( executingTestOrder == 2 ) {
classLevelAssertions( scope );
}
else {
throw new RuntimeException( "boom" );
}
}
}

View File

@ -0,0 +1,15 @@
/*
* 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.
*/
/**
* Testing the ability to apply testing annotations at method-level
*
* See https://hibernate.atlassian.net/browse/HHH-15344
*
* @author Steve Ebersole
*/
package org.hibernate.testing.annotations.methods;