Skip to content

Commit 996e66a

Browse files
committed
Perform NullAway build-time checks in spring-test
Closes gh-32475
1 parent 96d9081 commit 996e66a

27 files changed

+56
-10
lines changed

gradle/spring-module.gradle

+1-2
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ tasks.withType(JavaCompile).configureEach {
120120
option("NullAway:AnnotatedPackages", "org.springframework")
121121
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
122122
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
123-
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature," +
124-
"org.springframework.test,org.springframework.mock")
123+
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
125124
}
126125
}
127126
tasks.compileJava {

spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java

+1
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ private GenericApplicationContext loadContextForAotProcessing(
352352
}
353353
catch (Exception ex) {
354354
Throwable cause = (ex instanceof ContextLoadException cle ? cle.getCause() : ex);
355+
Assert.state(cause != null, "Cause must not be null");
355356
throw new TestContextAotException(
356357
"Failed to load ApplicationContext for AOT processing for test class [%s]"
357358
.formatted(testClass.getName()), cause);

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanPostProcessor.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ else if (enforceExistingDefinition) {
179179
registry.registerBeanDefinition(beanName, beanDefinition);
180180

181181
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
182+
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
182183
if (this.beanFactory.isSingleton(beanName)) {
183184
// Now we have an instance (the override) that we can register.
184185
// At this stage we don't expect a singleton instance to be present,
@@ -222,6 +223,7 @@ protected final Object wrapIfNecessary(Object bean, String beanName) throws Bean
222223
final OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
223224
if (metadata != null && metadata.getBeanOverrideStrategy() == BeanOverrideStrategy.WRAP_EARLY_BEAN) {
224225
bean = metadata.createOverride(beanName, null, bean);
226+
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
225227
metadata.track(bean, this.beanFactory);
226228
}
227229
return bean;
@@ -234,6 +236,7 @@ private RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
234236
}
235237

236238
private Set<String> getExistingBeanNames(ResolvableType resolvableType) {
239+
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
237240
Set<String> beans = new LinkedHashSet<>(
238241
Arrays.asList(this.beanFactory.getBeanNamesForType(resolvableType, true, false)));
239242
Class<?> type = resolvableType.resolve(Object.class);
@@ -274,6 +277,7 @@ private void inject(Field field, Object target, String beanName) {
274277
try {
275278
ReflectionUtils.makeAccessible(field);
276279
Object existingValue = ReflectionUtils.getField(field, target);
280+
Assert.state(this.beanFactory != null, "ConfigurableListableBeanFactory must not be null");
277281
Object bean = this.beanFactory.getBean(beanName, field.getType());
278282
if (existingValue == bean) {
279283
return;
@@ -308,7 +312,7 @@ public static void register(BeanDefinitionRegistry registry, @Nullable Set<Overr
308312
constructorArgs.addIndexedArgumentValue(0, new LinkedHashSet<OverrideMetadata>()));
309313
ConstructorArgumentValues.ValueHolder constructorArg =
310314
definition.getConstructorArgumentValues().getIndexedArgumentValue(0, Set.class);
311-
@SuppressWarnings("unchecked")
315+
@SuppressWarnings({"unchecked", "NullAway"})
312316
Set<OverrideMetadata> existing = (Set<OverrideMetadata>) constructorArg.getValue();
313317
if (overrideMetadata != null && existing != null) {
314318
existing.addAll(overrideMetadata);

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2323
import org.springframework.context.ConfigurableApplicationContext;
24+
import org.springframework.lang.Nullable;
2425
import org.springframework.test.context.ContextConfigurationAttributes;
2526
import org.springframework.test.context.ContextCustomizer;
2627
import org.springframework.test.context.ContextCustomizerFactory;
@@ -37,6 +38,7 @@
3738
public class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
3839

3940
@Override
41+
@Nullable
4042
public ContextCustomizer createContextCustomizer(Class<?> testClass,
4143
List<ContextConfigurationAttributes> configAttributes) {
4244

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParser.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.beans.BeanUtils;
2727
import org.springframework.core.ResolvableType;
28+
import org.springframework.core.annotation.MergedAnnotation;
2829
import org.springframework.core.annotation.MergedAnnotations;
2930
import org.springframework.util.Assert;
3031
import org.springframework.util.ReflectionUtils;
@@ -96,7 +97,9 @@ private void parseField(Field field, Class<?> source) {
9697

9798
BeanOverride beanOverride = mergedAnnotation.synthesize();
9899
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
99-
Annotation composedAnnotation = mergedAnnotation.getMetaSource().synthesize();
100+
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
101+
Assert.state(metaSource != null, "Meta-annotation source must not be null");
102+
Annotation composedAnnotation = metaSource.synthesize();
100103
ResolvableType typeToOverride = processor.getOrDeduceType(field, composedAnnotation, source);
101104

102105
Assert.state(overrideAnnotationFound.compareAndSet(false, true),

spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockDefinition.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public String getBeanOverrideDescription() {
7575
}
7676

7777
@Override
78-
protected Object createOverride(String beanName, BeanDefinition existingBeanDefinition, Object existingBeanInstance) {
78+
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
7979
return createMock(beanName);
8080
}
8181

spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.context.ConfigurableApplicationContext;
3232
import org.springframework.core.NativeDetector;
3333
import org.springframework.core.Ordered;
34+
import org.springframework.lang.Nullable;
3435
import org.springframework.test.context.TestContext;
3536
import org.springframework.test.context.support.AbstractTestExecutionListener;
3637

@@ -101,6 +102,7 @@ private void resetMocks(ConfigurableApplicationContext applicationContext, MockR
101102
}
102103
}
103104

105+
@Nullable
104106
private Object getBean(ConfigurableListableBeanFactory beanFactory, String name) {
105107
try {
106108
if (isStandardBeanOrSingletonFactoryBean(beanFactory, name)) {

spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java

+1
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ private String[] getScripts(Sql sql, Class<?> testClass, @Nullable Method testMe
412412
* Detect a default SQL script by implementing the algorithm defined in
413413
* {@link Sql#scripts}.
414414
*/
415+
@SuppressWarnings("NullAway")
415416
private String detectDefaultScript(Class<?> testClass, @Nullable Method testMethod, boolean classLevel) {
416417
Assert.state(classLevel || testMethod != null, "Method-level @Sql requires a testMethod");
417418

spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java

+1
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ private static Store getStore(ExtensionContext context) {
377377
* the supplied {@link TestContextManager}.
378378
* @since 6.1
379379
*/
380+
@SuppressWarnings("NullAway")
380381
private static void registerMethodInvoker(TestContextManager testContextManager, ExtensionContext context) {
381382
testContextManager.getTestContext().setMethodInvoker(context.getExecutableInvoker()::invoke);
382383
}

spring-test/src/main/java/org/springframework/test/context/support/AbstractDirtiesContextTestExecutionListener.java

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ protected void dirtyContext(TestContext testContext, @Nullable HierarchyMode hie
8484
* @since 4.2
8585
* @see #dirtyContext
8686
*/
87+
@SuppressWarnings("NullAway")
8788
protected void beforeOrAfterTestMethod(TestContext testContext, MethodMode requiredMethodMode,
8889
ClassMode requiredClassMode) throws Exception {
8990

@@ -135,6 +136,7 @@ else if (logger.isDebugEnabled()) {
135136
* @since 4.2
136137
* @see #dirtyContext
137138
*/
139+
@SuppressWarnings("NullAway")
138140
protected void beforeOrAfterTestClass(TestContext testContext, ClassMode requiredClassMode) throws Exception {
139141
Assert.notNull(testContext, "TestContext must not be null");
140142
Assert.notNull(requiredClassMode, "requiredClassMode must not be null");

spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ static Map<String, List<ContextConfigurationAttributes>> buildContextHierarchyMa
232232
* @throws IllegalArgumentException if the supplied class is {@code null} or if
233233
* {@code @ContextConfiguration} is not <em>present</em> on the supplied class
234234
*/
235+
@SuppressWarnings("NullAway")
235236
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
236237
Assert.notNull(testClass, "Class must not be null");
237238

spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ else if (!duplicationDetected(currentAttributes, previousAttributes)) {
135135
return mergedAttributes;
136136
}
137137

138+
@SuppressWarnings("NullAway")
138139
private static boolean duplicationDetected(TestPropertySourceAttributes currentAttributes,
139140
@Nullable TestPropertySourceAttributes previousAttributes) {
140141

spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java

+2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ public final int getOrder() {
196196
* @see #getTransactionManager(TestContext, String)
197197
*/
198198
@Override
199+
@SuppressWarnings("NullAway")
199200
public void beforeTestMethod(final TestContext testContext) throws Exception {
200201
Method testMethod = testContext.getTestMethod();
201202
Class<?> testClass = testContext.getTestClass();
@@ -414,6 +415,7 @@ protected PlatformTransactionManager getTransactionManager(TestContext testConte
414415
* @return the <em>default rollback</em> flag for the supplied test context
415416
* @throws Exception if an error occurs while determining the default rollback flag
416417
*/
418+
@SuppressWarnings("NullAway")
417419
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
418420
Class<?> testClass = testContext.getTestClass();
419421
Rollback rollback = TestContextAnnotationUtils.findMergedAnnotation(testClass, Rollback.class);

spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ public String getResourceBasePath() {
218218
*/
219219
@Override
220220
public boolean equals(@Nullable Object other) {
221-
return (this == other || (super.equals(other) &&
222-
this.resourceBasePath.equals(((WebMergedContextConfiguration) other).resourceBasePath)));
221+
return (this == other || (super.equals(other) && other instanceof WebMergedContextConfiguration otherConfiguration &&
222+
this.resourceBasePath.equals(otherConfiguration.resourceBasePath)));
223223
}
224224

225225
/**

spring-test/src/main/java/org/springframework/test/http/MediaTypeAssert.java

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public MediaTypeAssert isCompatibleWith(String mediaType) {
9191
}
9292

9393

94+
@SuppressWarnings("NullAway")
9495
private MediaType parseMediaType(String value) {
9596
try {
9697
return MediaType.parseMediaType(value);

spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.test.util;
1818

19+
import org.springframework.lang.Contract;
1920
import org.springframework.lang.Nullable;
2021
import org.springframework.util.ObjectUtils;
2122

@@ -33,6 +34,7 @@ public abstract class AssertionErrors {
3334
* Fail a test with the given message.
3435
* @param message a message that describes the reason for the failure
3536
*/
37+
@Contract("_ -> fail")
3638
public static void fail(String message) {
3739
throw new AssertionError(message);
3840
}
@@ -65,6 +67,7 @@ public static void fail(String message, @Nullable Object expected, @Nullable Obj
6567
* @param message a message that describes the reason for the failure
6668
* @param condition the condition to test for
6769
*/
70+
@Contract("_, false -> fail")
6871
public static void assertTrue(String message, boolean condition) {
6972
if (!condition) {
7073
fail(message);
@@ -78,6 +81,7 @@ public static void assertTrue(String message, boolean condition) {
7881
* @param condition the condition to test for
7982
* @since 5.2.1
8083
*/
84+
@Contract("_, true -> fail")
8185
public static void assertFalse(String message, boolean condition) {
8286
if (condition) {
8387
fail(message);
@@ -91,6 +95,7 @@ public static void assertFalse(String message, boolean condition) {
9195
* @param object the object to check
9296
* @since 5.2.1
9397
*/
98+
@Contract("_, !null -> fail")
9499
public static void assertNull(String message, @Nullable Object object) {
95100
assertTrue(message, object == null);
96101
}
@@ -102,6 +107,7 @@ public static void assertNull(String message, @Nullable Object object) {
102107
* @param object the object to check
103108
* @since 5.1.8
104109
*/
110+
@Contract("_, null -> fail")
105111
public static void assertNotNull(String message, @Nullable Object object) {
106112
assertTrue(message, object != null);
107113
}

spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java

+2
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public static void setField(
172172
* @see ReflectionUtils#setField(Field, Object, Object)
173173
* @see AopTestUtils#getUltimateTargetObject(Object)
174174
*/
175+
@SuppressWarnings("NullAway")
175176
public static void setField(@Nullable Object targetObject, @Nullable Class<?> targetClass,
176177
@Nullable String name, @Nullable Object value, @Nullable Class<?> type) {
177178

@@ -259,6 +260,7 @@ public static Object getField(Class<?> targetClass, String name) {
259260
* @see AopTestUtils#getUltimateTargetObject(Object)
260261
*/
261262
@Nullable
263+
@SuppressWarnings("NullAway")
262264
public static Object getField(@Nullable Object targetObject, @Nullable Class<?> targetClass, String name) {
263265
Assert.isTrue(targetObject != null || targetClass != null,
264266
"Either targetObject or targetClass for the field must be specified");

spring-test/src/main/java/org/springframework/test/web/ModelAndViewAssert.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public abstract class ModelAndViewAssert {
5353
* @param expectedType expected type of the model value
5454
* @return the model value
5555
*/
56-
@SuppressWarnings("unchecked")
56+
@SuppressWarnings({"unchecked", "NullAway"})
5757
public static <T> T assertAndReturnModelAttributeOfType(ModelAndView mav, String modelName, Class<T> expectedType) {
5858
Map<String, Object> model = mav.getModel();
5959
Object obj = model.get(modelName);
@@ -109,6 +109,7 @@ public static void assertModelAttributeValue(ModelAndView mav, String modelName,
109109
* @param mav the ModelAndView to test against (never {@code null})
110110
* @param expectedModel the expected model
111111
*/
112+
@SuppressWarnings("NullAway")
112113
public static void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
113114
Map<String, Object> model = mav.getModel();
114115

spring-test/src/main/java/org/springframework/test/web/UriAssert.java

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public UriAssert matchesAntPattern(String uriPattern) {
8080
return this;
8181
}
8282

83+
@SuppressWarnings("NullAway")
8384
private String buildUri(String uriTemplate, Object... uriVars) {
8485
try {
8586
return UriComponentsBuilder.fromUriString(uriTemplate)

spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public static RequestMatcher queryParamList(String name, Matcher<? super List<St
158158
* @see #queryParam(String, String...)
159159
*/
160160
@SafeVarargs
161+
@SuppressWarnings("NullAway")
161162
public static RequestMatcher queryParam(String name, Matcher<? super String>... matchers) {
162163
return request -> {
163164
MultiValueMap<String, String> params = getQueryParams(request);
@@ -185,6 +186,7 @@ public static RequestMatcher queryParam(String name, Matcher<? super String>...
185186
* @see #queryParamList(String, Matcher)
186187
* @see #queryParam(String, Matcher...)
187188
*/
189+
@SuppressWarnings("NullAway")
188190
public static RequestMatcher queryParam(String name, String... expectedValues) {
189191
return request -> {
190192
MultiValueMap<String, String> params = getQueryParams(request);
@@ -362,7 +364,7 @@ private static void assertValueCount(
362364
if (values == null) {
363365
fail(message + " to exist but was null");
364366
}
365-
if (count > values.size()) {
367+
else if (count > values.size()) {
366368
fail(message + " to have at least <" + count + "> values but found " + values);
367369
}
368370
}

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

+1
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ public ResponseSpec exchange() {
374374
DefaultWebTestClient.this.entityResultConsumer, getResponseTimeout());
375375
}
376376

377+
@SuppressWarnings("NullAway")
377378
private ClientRequest.Builder initRequestBuilder() {
378379
return ClientRequest.create(this.httpMethod, initUri())
379380
.headers(headersToUse -> {

spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class WiretapConnector implements ClientHttpConnector {
6262

6363

6464
@Override
65+
@SuppressWarnings("NullAway")
6566
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
6667
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
6768

@@ -181,6 +182,7 @@ public Publisher<? extends Publisher<? extends DataBuffer>> getNestedPublisherTo
181182
return this.publisherNested;
182183
}
183184

185+
@SuppressWarnings("NullAway")
184186
public Mono<byte[]> getContent() {
185187
return Mono.defer(() -> {
186188
if (this.content.scan(Scannable.Attr.TERMINATED) == Boolean.TRUE) {

spring-test/src/main/java/org/springframework/test/web/servlet/DefaultMvcResult.java

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public Object getAsyncResult() {
137137
}
138138

139139
@Override
140+
@SuppressWarnings("NullAway")
140141
public Object getAsyncResult(long timeToWait) {
141142
if (this.mockRequest.getAsyncContext() != null && timeToWait == -1) {
142143
long requestTimeout = this.mockRequest.getAsyncContext().getTimeout();

spring-test/src/main/java/org/springframework/test/web/servlet/assertj/DefaultAssertableMvcResult.java

+4
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,25 @@ public MockHttpServletResponse getResponse() {
6868
}
6969

7070
@Override
71+
@Nullable
7172
public Object getHandler() {
7273
return getTarget().getHandler();
7374
}
7475

7576
@Override
77+
@Nullable
7678
public HandlerInterceptor[] getInterceptors() {
7779
return getTarget().getInterceptors();
7880
}
7981

8082
@Override
83+
@Nullable
8184
public ModelAndView getModelAndView() {
8285
return getTarget().getModelAndView();
8386
}
8487

8588
@Override
89+
@Nullable
8690
public Exception getResolvedException() {
8791
return getTarget().getResolvedException();
8892
}

spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcResultAssert.java

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public MvcResultAssert hasViewName(String viewName) {
203203
}
204204

205205

206+
@SuppressWarnings("NullAway")
206207
private ModelAndView getModelAndView() {
207208
ModelAndView modelAndView = this.actual.getModelAndView();
208209
Assertions.assertThat(modelAndView).as("ModelAndView").isNotNull();

0 commit comments

Comments
 (0)