Perform NullAway build-time checks in spring-test

Closes gh-32475
This commit is contained in:
Sébastien Deleuze 2024-03-26 18:14:56 +01:00
parent 96d9081190
commit 996e66abdb
27 changed files with 56 additions and 10 deletions

View File

@ -120,8 +120,7 @@ tasks.withType(JavaCompile).configureEach {
option("NullAway:AnnotatedPackages", "org.springframework")
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature," +
"org.springframework.test,org.springframework.mock")
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
}
}
tasks.compileJava {

View File

@ -352,6 +352,7 @@ public class TestContextAotGenerator {
}
catch (Exception ex) {
Throwable cause = (ex instanceof ContextLoadException cle ? cle.getCause() : ex);
Assert.state(cause != null, "Cause must not be null");
throw new TestContextAotException(
"Failed to load ApplicationContext for AOT processing for test class [%s]"
.formatted(testClass.getName()), cause);

View File

@ -179,6 +179,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
registry.registerBeanDefinition(beanName, beanDefinition);
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
if (this.beanFactory.isSingleton(beanName)) {
// Now we have an instance (the override) that we can register.
// At this stage we don't expect a singleton instance to be present,
@ -222,6 +223,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
final OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
if (metadata != null && metadata.getBeanOverrideStrategy() == BeanOverrideStrategy.WRAP_EARLY_BEAN) {
bean = metadata.createOverride(beanName, null, bean);
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
metadata.track(bean, this.beanFactory);
}
return bean;
@ -234,6 +236,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
}
private Set<String> getExistingBeanNames(ResolvableType resolvableType) {
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
Set<String> beans = new LinkedHashSet<>(
Arrays.asList(this.beanFactory.getBeanNamesForType(resolvableType, true, false)));
Class<?> type = resolvableType.resolve(Object.class);
@ -274,6 +277,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
try {
ReflectionUtils.makeAccessible(field);
Object existingValue = ReflectionUtils.getField(field, target);
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
Object bean = this.beanFactory.getBean(beanName, field.getType());
if (existingValue == bean) {
return;
@ -308,7 +312,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
constructorArgs.addIndexedArgumentValue(0, new LinkedHashSet<OverrideMetadata>()));
ConstructorArgumentValues.ValueHolder constructorArg =
definition.getConstructorArgumentValues().getIndexedArgumentValue(0, Set.class);
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "NullAway"})
Set<OverrideMetadata> existing = (Set<OverrideMetadata>) constructorArg.getValue();
if (overrideMetadata != null && existing != null) {
existing.addAll(overrideMetadata);

View File

@ -21,6 +21,7 @@ import java.util.Set;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
@ -37,6 +38,7 @@ import org.springframework.test.context.TestContextAnnotationUtils;
public class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
@Override
@Nullable
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {

View File

@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@ -96,7 +97,9 @@ class BeanOverrideParser {
BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
Annotation composedAnnotation = mergedAnnotation.getMetaSource().synthesize();
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "Meta-annotation source must not be null");
Annotation composedAnnotation = metaSource.synthesize();
ResolvableType typeToOverride = processor.getOrDeduceType(field, composedAnnotation, source);
Assert.state(overrideAnnotationFound.compareAndSet(false, true),

View File

@ -75,7 +75,7 @@ class MockDefinition extends Definition {
}
@Override
protected Object createOverride(String beanName, BeanDefinition existingBeanDefinition, Object existingBeanInstance) {
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
return createMock(beanName);
}

View File

@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.NativeDetector;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@ -101,6 +102,7 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
}
}
@Nullable
private Object getBean(ConfigurableListableBeanFactory beanFactory, String name) {
try {
if (isStandardBeanOrSingletonFactoryBean(beanFactory, name)) {

View File

@ -412,6 +412,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* Detect a default SQL script by implementing the algorithm defined in
* {@link Sql#scripts}.
*/
@SuppressWarnings("NullAway")
private String detectDefaultScript(Class<?> testClass, @Nullable Method testMethod, boolean classLevel) {
Assert.state(classLevel || testMethod != null, "Method-level @Sql requires a testMethod");

View File

@ -377,6 +377,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* the supplied {@link TestContextManager}.
* @since 6.1
*/
@SuppressWarnings("NullAway")
private static void registerMethodInvoker(TestContextManager testContextManager, ExtensionContext context) {
testContextManager.getTestContext().setMethodInvoker(context.getExecutableInvoker()::invoke);
}

View File

@ -84,6 +84,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra
* @since 4.2
* @see #dirtyContext
*/
@SuppressWarnings("NullAway")
protected void beforeOrAfterTestMethod(TestContext testContext, MethodMode requiredMethodMode,
ClassMode requiredClassMode) throws Exception {
@ -135,6 +136,7 @@ public abstract class AbstractDirtiesContextTestExecutionListener extends Abstra
* @since 4.2
* @see #dirtyContext
*/
@SuppressWarnings("NullAway")
protected void beforeOrAfterTestClass(TestContext testContext, ClassMode requiredClassMode) throws Exception {
Assert.notNull(testContext, "TestContext must not be null");
Assert.notNull(requiredClassMode, "requiredClassMode must not be null");

View File

@ -232,6 +232,7 @@ abstract class ContextLoaderUtils {
* @throws IllegalArgumentException if the supplied class is {@code null} or if
* {@code @ContextConfiguration} is not <em>present</em> on the supplied class
*/
@SuppressWarnings("NullAway")
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");

View File

@ -135,6 +135,7 @@ public abstract class TestPropertySourceUtils {
return mergedAttributes;
}
@SuppressWarnings("NullAway")
private static boolean duplicationDetected(TestPropertySourceAttributes currentAttributes,
@Nullable TestPropertySourceAttributes previousAttributes) {

View File

@ -196,6 +196,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see #getTransactionManager(TestContext, String)
*/
@Override
@SuppressWarnings("NullAway")
public void beforeTestMethod(final TestContext testContext) throws Exception {
Method testMethod = testContext.getTestMethod();
Class<?> testClass = testContext.getTestClass();
@ -414,6 +415,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @return the <em>default rollback</em> flag for the supplied test context
* @throws Exception if an error occurs while determining the default rollback flag
*/
@SuppressWarnings("NullAway")
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
Rollback rollback = TestContextAnnotationUtils.findMergedAnnotation(testClass, Rollback.class);

View File

@ -218,8 +218,8 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (super.equals(other) &&
this.resourceBasePath.equals(((WebMergedContextConfiguration) other).resourceBasePath)));
return (this == other || (super.equals(other) && other instanceof WebMergedContextConfiguration otherConfiguration &&
this.resourceBasePath.equals(otherConfiguration.resourceBasePath)));
}
/**

View File

@ -91,6 +91,7 @@ public class MediaTypeAssert extends AbstractObjectAssert<MediaTypeAssert, Media
}
@SuppressWarnings("NullAway")
private MediaType parseMediaType(String value) {
try {
return MediaType.parseMediaType(value);

View File

@ -16,6 +16,7 @@
package org.springframework.test.util;
import org.springframework.lang.Contract;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@ -33,6 +34,7 @@ public abstract class AssertionErrors {
* Fail a test with the given message.
* @param message a message that describes the reason for the failure
*/
@Contract("_ -> fail")
public static void fail(String message) {
throw new AssertionError(message);
}
@ -65,6 +67,7 @@ public abstract class AssertionErrors {
* @param message a message that describes the reason for the failure
* @param condition the condition to test for
*/
@Contract("_, false -> fail")
public static void assertTrue(String message, boolean condition) {
if (!condition) {
fail(message);
@ -78,6 +81,7 @@ public abstract class AssertionErrors {
* @param condition the condition to test for
* @since 5.2.1
*/
@Contract("_, true -> fail")
public static void assertFalse(String message, boolean condition) {
if (condition) {
fail(message);
@ -91,6 +95,7 @@ public abstract class AssertionErrors {
* @param object the object to check
* @since 5.2.1
*/
@Contract("_, !null -> fail")
public static void assertNull(String message, @Nullable Object object) {
assertTrue(message, object == null);
}
@ -102,6 +107,7 @@ public abstract class AssertionErrors {
* @param object the object to check
* @since 5.1.8
*/
@Contract("_, null -> fail")
public static void assertNotNull(String message, @Nullable Object object) {
assertTrue(message, object != null);
}

View File

@ -172,6 +172,7 @@ public abstract class ReflectionTestUtils {
* @see ReflectionUtils#setField(Field, Object, Object)
* @see AopTestUtils#getUltimateTargetObject(Object)
*/
@SuppressWarnings("NullAway")
public static void setField(@Nullable Object targetObject, @Nullable Class<?> targetClass,
@Nullable String name, @Nullable Object value, @Nullable Class<?> type) {
@ -259,6 +260,7 @@ public abstract class ReflectionTestUtils {
* @see AopTestUtils#getUltimateTargetObject(Object)
*/
@Nullable
@SuppressWarnings("NullAway")
public static Object getField(@Nullable Object targetObject, @Nullable Class<?> targetClass, String name) {
Assert.isTrue(targetObject != null || targetClass != null,
"Either targetObject or targetClass for the field must be specified");

View File

@ -53,7 +53,7 @@ public abstract class ModelAndViewAssert {
* @param expectedType expected type of the model value
* @return the model value
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "NullAway"})
public static <T> T assertAndReturnModelAttributeOfType(ModelAndView mav, String modelName, Class<T> expectedType) {
Map<String, Object> model = mav.getModel();
Object obj = model.get(modelName);
@ -109,6 +109,7 @@ public abstract class ModelAndViewAssert {
* @param mav the ModelAndView to test against (never {@code null})
* @param expectedModel the expected model
*/
@SuppressWarnings("NullAway")
public static void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
Map<String, Object> model = mav.getModel();

View File

@ -80,6 +80,7 @@ public class UriAssert extends AbstractStringAssert<UriAssert> {
return this;
}
@SuppressWarnings("NullAway")
private String buildUri(String uriTemplate, Object... uriVars) {
try {
return UriComponentsBuilder.fromUriString(uriTemplate)

View File

@ -158,6 +158,7 @@ public abstract class MockRestRequestMatchers {
* @see #queryParam(String, String...)
*/
@SafeVarargs
@SuppressWarnings("NullAway")
public static RequestMatcher queryParam(String name, Matcher<? super String>... matchers) {
return request -> {
MultiValueMap<String, String> params = getQueryParams(request);
@ -185,6 +186,7 @@ public abstract class MockRestRequestMatchers {
* @see #queryParamList(String, Matcher)
* @see #queryParam(String, Matcher...)
*/
@SuppressWarnings("NullAway")
public static RequestMatcher queryParam(String name, String... expectedValues) {
return request -> {
MultiValueMap<String, String> params = getQueryParams(request);
@ -362,7 +364,7 @@ public abstract class MockRestRequestMatchers {
if (values == null) {
fail(message + " to exist but was null");
}
if (count > values.size()) {
else if (count > values.size()) {
fail(message + " to have at least <" + count + "> values but found " + values);
}
}

View File

@ -374,6 +374,7 @@ class DefaultWebTestClient implements WebTestClient {
DefaultWebTestClient.this.entityResultConsumer, getResponseTimeout());
}
@SuppressWarnings("NullAway")
private ClientRequest.Builder initRequestBuilder() {
return ClientRequest.create(this.httpMethod, initUri())
.headers(headersToUse -> {

View File

@ -62,6 +62,7 @@ class WiretapConnector implements ClientHttpConnector {
@Override
@SuppressWarnings("NullAway")
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
@ -181,6 +182,7 @@ class WiretapConnector implements ClientHttpConnector {
return this.publisherNested;
}
@SuppressWarnings("NullAway")
public Mono<byte[]> getContent() {
return Mono.defer(() -> {
if (this.content.scan(Scannable.Attr.TERMINATED) == Boolean.TRUE) {

View File

@ -137,6 +137,7 @@ class DefaultMvcResult implements MvcResult {
}
@Override
@SuppressWarnings("NullAway")
public Object getAsyncResult(long timeToWait) {
if (this.mockRequest.getAsyncContext() != null && timeToWait == -1) {
long requestTimeout = this.mockRequest.getAsyncContext().getTimeout();

View File

@ -68,21 +68,25 @@ final class DefaultAssertableMvcResult implements AssertableMvcResult {
}
@Override
@Nullable
public Object getHandler() {
return getTarget().getHandler();
}
@Override
@Nullable
public HandlerInterceptor[] getInterceptors() {
return getTarget().getInterceptors();
}
@Override
@Nullable
public ModelAndView getModelAndView() {
return getTarget().getModelAndView();
}
@Override
@Nullable
public Exception getResolvedException() {
return getTarget().getResolvedException();
}

View File

@ -203,6 +203,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert<MvcRe
}
@SuppressWarnings("NullAway")
private ModelAndView getModelAndView() {
ModelAndView modelAndView = this.actual.getModelAndView();
Assertions.assertThat(modelAndView).as("ModelAndView").isNotNull();

View File

@ -22,6 +22,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.Assert;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.util.AssertionErrors.assertEquals;
@ -105,7 +106,9 @@ public class StatusResultMatchers {
}
private HttpStatus.Series getHttpStatusSeries(MvcResult result) {
return HttpStatus.Series.resolve(result.getResponse().getStatus());
HttpStatus.Series series = HttpStatus.Series.resolve(result.getResponse().getStatus());
Assert.state(series != null, "HTTP status series must not be null");
return series;
}
/**

View File

@ -47,6 +47,7 @@ public class ViewResultMatchers {
/**
* Assert the selected view name with the given Hamcrest {@link Matcher}.
*/
@SuppressWarnings("NullAway")
public ResultMatcher name(Matcher<? super String> matcher) {
return result -> {
ModelAndView mav = result.getModelAndView();
@ -60,6 +61,7 @@ public class ViewResultMatchers {
/**
* Assert the selected view name.
*/
@SuppressWarnings("NullAway")
public ResultMatcher name(String expectedViewName) {
return result -> {
ModelAndView mav = result.getModelAndView();