Provide dedicated AOT exception hierarchy

This commit adds a number of catch point that provides additional
context when an AOT processor fails to execute. Amongst other things,
this makes sure that the bean name and its descriptor is consistently
provided in the error message when available.

Closes gh-32777
This commit is contained in:
Stéphane Nicoll
2024-06-03 15:45:15 +02:00
parent f31113e325
commit 42ace2c2c9
13 changed files with 294 additions and 33 deletions

View File

@@ -69,7 +69,7 @@ import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
@@ -691,9 +691,10 @@ class BeanDefinitionMethodGeneratorTests {
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
List.of());
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
assertThatExceptionOfType(AotBeanProcessingException.class)
.isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode))
.withMessage("Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test
@@ -709,9 +710,10 @@ class BeanDefinitionMethodGeneratorTests {
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
List.of(aotContribution));
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
assertThatExceptionOfType(AotBeanProcessingException.class)
.isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode))
.withMessage("Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test
@@ -728,9 +730,10 @@ class BeanDefinitionMethodGeneratorTests {
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
List.of(aotContribution));
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
assertThatExceptionOfType(AotBeanProcessingException.class)
.isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode))
.withMessage("Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test

View File

@@ -29,6 +29,7 @@ import org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.generate.ValueCodeGenerationException;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration;
@@ -38,6 +39,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.AgeHolder;
import org.springframework.beans.testfixture.beans.Employee;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.NestedTestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanFactoryInitializationCode;
import org.springframework.core.test.io.support.MockSpringFactoriesLoader;
@@ -50,6 +52,7 @@ import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
/**
@@ -156,6 +159,57 @@ class BeanRegistrationsAotContributionTests {
.accepts(this.generationContext.getRuntimeHints());
}
@Test
void applyToFailingDoesNotWrapAotException() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class);
beanDefinition.setInstanceSupplier(TestBean::new);
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory,
registeredBean, null, List.of());
BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias");
assertThatExceptionOfType(AotProcessingException.class)
.isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode))
.withMessage("Error processing bean with name 'testBean': instance supplier is not supported")
.withNoCause();
}
@Test
void applyToFailingWrapsValueCodeGeneration() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class);
beanDefinition.getPropertyValues().addPropertyValue("doctor", new NestedTestBean());
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory,
registeredBean, null, List.of());
BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias");
assertThatExceptionOfType(AotProcessingException.class)
.isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode))
.withMessage("Error processing bean with name 'testBean': failed to generate code for bean definition")
.havingCause().isInstanceOf(ValueCodeGenerationException.class)
.withMessageContaining("Failed to generate code for")
.withMessageContaining(NestedTestBean.class.getName());
}
@Test
void applyToFailingProvidesDedicatedException() {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory,
registeredBean, null, List.of()) {
@Override
MethodReference generateBeanDefinitionMethod(GenerationContext generationContext,
BeanRegistrationsCode beanRegistrationsCode) {
throw new IllegalStateException("Test exception");
}
};
BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias");
assertThatExceptionOfType(AotProcessingException.class)
.isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode))
.withMessage("Error processing bean with name 'testBean': failed to generate code for bean definition")
.havingCause().isInstanceOf(IllegalStateException.class).withMessage("Test exception");
}
private RegisteredBean registerBean(RootBeanDefinition rootBeanDefinition) {
String beanName = "testBean";
this.beanFactory.registerBeanDefinition(beanName, rootBeanDefinition);

View File

@@ -48,7 +48,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -72,7 +72,8 @@ class DefaultBeanRegistrationCodeFragmentsTests {
beanDefinition.setInstanceSupplier(SimpleBean::new);
RegisteredBean registeredBean = registerTestBean(beanDefinition);
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
assertThatExceptionOfType(AotBeanProcessingException.class)
.isThrownBy(() -> codeFragments.getTarget(registeredBean))
.withMessageContaining("Error processing bean with name 'testBean': instance supplier is not supported");
}
@@ -83,7 +84,8 @@ class DefaultBeanRegistrationCodeFragmentsTests {
beanDefinition.setResourceDescription("my test resource");
RegisteredBean registeredBean = registerTestBean(beanDefinition);
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
assertThatExceptionOfType(AotBeanProcessingException.class)
.isThrownBy(() -> codeFragments.getTarget(registeredBean))
.withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: "
+ "instance supplier is not supported");
}

View File

@@ -102,7 +102,7 @@ class InstanceSupplierCodeGeneratorKotlinTests {
this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.genericBeanDefinition(KotlinConfiguration::class.java).beanDefinition
)
Assertions.assertThatIllegalStateException().isThrownBy {
Assertions.assertThatExceptionOfType(AotBeanProcessingException::class.java).isThrownBy {
compile(beanFactory, beanDefinition) { _, _ -> }
}
}