Browse Source

Add nullability annotations to core/spring-boot-test

See gh-46587
pull/46595/head
Moritz Halbritter 9 months ago
parent
commit
bac4a38fe8
  1. 12
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java
  2. 8
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java
  3. 7
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java
  4. 4
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java
  5. 40
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java
  6. 4
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java
  7. 11
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
  8. 46
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java
  9. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java
  10. 14
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java
  11. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java
  12. 4
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java
  13. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java
  14. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java
  15. 26
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java
  16. 8
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java
  17. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java
  18. 29
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java
  19. 39
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java
  20. 16
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java
  21. 7
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java
  22. 30
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java
  23. 8
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonLoader.java
  24. 5
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java
  25. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/json/package-info.java
  26. 6
      core/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java
  27. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/package-info.java
  28. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/rsocket/server/package-info.java
  29. 6
      core/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java
  30. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java
  31. 4
      core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java
  32. 40
      core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java
  33. 3
      core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java

12
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java

@ -22,6 +22,8 @@ import java.util.LinkedHashMap; @@ -22,6 +22,8 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
@ -63,7 +65,7 @@ public final class AnnotatedClassFinder { @@ -63,7 +65,7 @@ public final class AnnotatedClassFinder {
* @return the first {@link Class} annotated with the target annotation within the
* hierarchy defined by the given {@code source} or {@code null} if none is found.
*/
public Class<?> findFromClass(Class<?> source) {
public @Nullable Class<?> findFromClass(Class<?> source) {
Assert.notNull(source, "'source' must not be null");
return findFromPackage(ClassUtils.getPackageName(source));
}
@ -75,7 +77,7 @@ public final class AnnotatedClassFinder { @@ -75,7 +77,7 @@ public final class AnnotatedClassFinder {
* @return the first {@link Class} annotated with the target annotation within the
* hierarchy defined by the given {@code source} or {@code null} if none is found.
*/
public Class<?> findFromPackage(String source) {
public @Nullable Class<?> findFromPackage(String source) {
Assert.notNull(source, "'source' must not be null");
Class<?> configuration = cache.get(source);
if (configuration == null) {
@ -85,13 +87,15 @@ public final class AnnotatedClassFinder { @@ -85,13 +87,15 @@ public final class AnnotatedClassFinder {
return configuration;
}
private Class<?> scanPackage(String source) {
private @Nullable Class<?> scanPackage(String source) {
while (!source.isEmpty()) {
Set<BeanDefinition> components = this.scanner.findCandidateComponents(source);
if (!components.isEmpty()) {
Assert.state(components.size() == 1, () -> "Found multiple @" + this.annotationType.getSimpleName()
+ " annotated classes " + components);
return ClassUtils.resolveClassName(components.iterator().next().getBeanClassName(), null);
String beanClassName = components.iterator().next().getBeanClassName();
Assert.state(beanClassName != null, "'beanClassName' must not be null");
return ClassUtils.resolveClassName(beanClassName, null);
}
source = getParentPackage(source);
}

8
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java

@ -27,6 +27,8 @@ import java.util.Collections; @@ -27,6 +27,8 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.core.SmartClassLoader;
import org.springframework.core.io.ClassPathResource;
@ -117,7 +119,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa @@ -117,7 +119,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa
}
@Override
public URL getResource(String name) {
public @Nullable URL getResource(String name) {
for (Predicate<String> filter : this.resourcesFilters) {
if (filter.test(name)) {
return null;
@ -137,7 +139,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa @@ -137,7 +139,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa
}
@Override
public InputStream getResourceAsStream(String name) {
public @Nullable InputStream getResourceAsStream(String name) {
for (Predicate<String> filter : this.resourcesFilters) {
if (filter.test(name)) {
return null;
@ -147,7 +149,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa @@ -147,7 +149,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa
}
@Override
public Class<?> publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain) {
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
for (Predicate<String> filter : this.classesFilters) {
if (filter.test(name)) {
throw new IllegalArgumentException(String.format("Defining class with name %s is not supported", name));

7
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java

@ -24,6 +24,8 @@ import java.util.LinkedHashSet; @@ -24,6 +24,8 @@ import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@ -156,6 +158,7 @@ class ImportsContextCustomizer implements ContextCustomizer { @@ -156,6 +158,7 @@ class ImportsContextCustomizer implements ContextCustomizer {
private static final String[] NO_IMPORTS = {};
@SuppressWarnings("NullAway.Init")
private ConfigurableListableBeanFactory beanFactory;
@Override
@ -254,7 +257,7 @@ class ImportsContextCustomizer implements ContextCustomizer { @@ -254,7 +257,7 @@ class ImportsContextCustomizer implements ContextCustomizer {
return ANNOTATION_FILTERS.stream().anyMatch((filter) -> filter.matches(typeName));
}
private Set<Object> determineImports(MergedAnnotations annotations, Class<?> testClass) {
private @Nullable Set<Object> determineImports(MergedAnnotations annotations, Class<?> testClass) {
Set<Object> determinedImports = new LinkedHashSet<>();
AnnotationMetadata metadata = AnnotationMetadata.introspect(testClass);
for (MergedAnnotation<Import> annotation : annotations.stream(Import.class).toList()) {
@ -269,7 +272,7 @@ class ImportsContextCustomizer implements ContextCustomizer { @@ -269,7 +272,7 @@ class ImportsContextCustomizer implements ContextCustomizer {
return determinedImports;
}
private Set<Object> determineImports(Class<?> source, AnnotationMetadata metadata) {
private @Nullable Set<Object> determineImports(Class<?> source, AnnotationMetadata metadata) {
if (DeterminableImports.class.isAssignableFrom(source)) {
// We can determine the imports
return ((DeterminableImports) instantiate(source)).determineImports(metadata);

4
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java

@ -19,6 +19,8 @@ package org.springframework.boot.test.context; @@ -19,6 +19,8 @@ package org.springframework.boot.test.context;
import java.lang.reflect.Method;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.AotDetector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@ -41,7 +43,7 @@ import org.springframework.util.ReflectionUtils; @@ -41,7 +43,7 @@ import org.springframework.util.ReflectionUtils;
class ImportsContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
public @Nullable ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (AotDetector.useGeneratedArtifacts()) {
return null;

40
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java

@ -23,6 +23,8 @@ import java.util.Collections; @@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.BeanUtils;
@ -105,6 +107,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -105,6 +107,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
private static final Consumer<SpringApplication> ALREADY_CONFIGURED = (springApplication) -> {
};
private static final Object NONE = new Object();
@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, Mode.STANDARD, null, null);
@ -123,8 +127,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -123,8 +127,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
}
private ApplicationContext loadContext(MergedContextConfiguration mergedConfig, Mode mode,
ApplicationContextInitializer<ConfigurableApplicationContext> initializer, RuntimeHints runtimeHints)
throws Exception {
@Nullable ApplicationContextInitializer<ConfigurableApplicationContext> initializer,
@Nullable RuntimeHints runtimeHints) throws Exception {
assertHasClassesOrLocations(mergedConfig);
SpringBootTestAnnotation annotation = SpringBootTestAnnotation.get(mergedConfig);
String[] args = annotation.getArgs();
@ -153,7 +157,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -153,7 +157,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
+ SpringVersion.getVersion() + ").");
}
private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) {
private @Nullable Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) {
if (useMainMethod == UseMainMethod.NEVER) {
return null;
}
@ -167,14 +171,16 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -167,14 +171,16 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
"Cannot use main method as no @SpringBootConfiguration-annotated class is available");
Method mainMethod = findMainMethod(springBootConfiguration);
Assert.state(mainMethod != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
() -> "Main method not found on '%s'".formatted(springBootConfiguration.getName()));
() -> "Main method not found on '%s'"
.formatted((springBootConfiguration != null) ? springBootConfiguration.getName() : null));
return mainMethod;
}
private static Method findMainMethod(Class<?> type) {
private static @Nullable Method findMainMethod(@Nullable Class<?> type) {
Method mainMethod = (type != null) ? ReflectionUtils.findMethod(type, "main", String[].class) : null;
if (mainMethod == null && KotlinDetector.isKotlinPresent()) {
try {
Assert.state(type != null, "'type' must not be null");
Class<?> kotlinClass = ClassUtils.forName(type.getName() + "Kt", type.getClassLoader());
mainMethod = ReflectionUtils.findMethod(kotlinClass, "main", String[].class);
}
@ -285,7 +291,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -285,7 +291,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
* method if you need a custom environment.
* @return a {@link ConfigurableEnvironment} instance
*/
protected ConfigurableEnvironment getEnvironment() {
protected @Nullable ConfigurableEnvironment getEnvironment() {
return null;
}
@ -462,9 +468,9 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -462,9 +468,9 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
private static class ParentContextApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final ApplicationContext parent;
private final @Nullable ApplicationContext parent;
ParentContextApplicationContextInitializer(ApplicationContext parent) {
ParentContextApplicationContextInitializer(@Nullable ApplicationContext parent) {
this.parent = parent;
}
@ -507,7 +513,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -507,7 +513,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
private final Mode mode;
private final ApplicationContextInitializer<ConfigurableApplicationContext> initializer;
private final @Nullable ApplicationContextInitializer<ConfigurableApplicationContext> initializer;
private final Consumer<SpringApplication> configurer;
@ -515,7 +521,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -515,7 +521,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
private final List<ApplicationContext> failedContexts = Collections.synchronizedList(new ArrayList<>());
ContextLoaderHook(Mode mode, ApplicationContextInitializer<ConfigurableApplicationContext> initializer,
ContextLoaderHook(Mode mode,
@Nullable ApplicationContextInitializer<ConfigurableApplicationContext> initializer,
Consumer<SpringApplication> configurer) {
this.mode = mode;
this.initializer = initializer;
@ -530,6 +537,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -530,6 +537,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
public void starting(ConfigurableBootstrapContext bootstrapContext) {
ContextLoaderHook.this.configurer.accept(application);
if (ContextLoaderHook.this.mode == Mode.AOT_RUNTIME) {
Assert.state(ContextLoaderHook.this.initializer != null, "'initializer' must not be null");
application.addInitializers(
(AotApplicationContextInitializer<?>) ContextLoaderHook.this.initializer::initialize);
}
@ -544,24 +552,24 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao @@ -544,24 +552,24 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
public void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) {
ContextLoaderHook.this.failedContexts.add(context);
}
};
}
private <T> ApplicationContext runMain(Runnable action) throws Exception {
private ApplicationContext runMain(Runnable action) throws Exception {
return run(() -> {
action.run();
return null;
return NONE;
});
}
private ApplicationContext run(ThrowingSupplier<ConfigurableApplicationContext> action) throws Exception {
private ApplicationContext run(ThrowingSupplier<?> action) throws Exception {
try {
ConfigurableApplicationContext context = SpringApplication.withHook(this, action);
if (context != null) {
Object result = SpringApplication.withHook(this, action);
if (result instanceof ApplicationContext context) {
return context;
}
}

4
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java

@ -19,6 +19,8 @@ package org.springframework.boot.test.context; @@ -19,6 +19,8 @@ package org.springframework.boot.test.context;
import java.util.Arrays;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ConfigurableApplicationContext;
@ -52,7 +54,7 @@ class SpringBootTestAnnotation implements ContextCustomizer { @@ -52,7 +54,7 @@ class SpringBootTestAnnotation implements ContextCustomizer {
this(TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class));
}
private SpringBootTestAnnotation(SpringBootTest annotation) {
private SpringBootTestAnnotation(@Nullable SpringBootTest annotation) {
this.args = (annotation != null) ? annotation.args() : NO_ARGS;
this.webEnvironment = (annotation != null) ? annotation.webEnvironment() : WebEnvironment.NONE;
this.useMainMethod = (annotation != null) ? annotation.useMainMethod() : UseMainMethod.NEVER;

11
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java

@ -25,6 +25,7 @@ import java.util.Set; @@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
@ -287,7 +288,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr @@ -287,7 +288,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
* the bootstrapper class as a property.
* @return the differentiator or {@code null}
*/
protected String getDifferentiatorPropertySourceProperty() {
protected @Nullable String getDifferentiatorPropertySourceProperty() {
return getClass().getName() + "=true";
}
@ -320,22 +321,22 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr @@ -320,22 +321,22 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
* @param testClass the source test class
* @return the {@link WebEnvironment} or {@code null}
*/
protected WebEnvironment getWebEnvironment(Class<?> testClass) {
protected @Nullable WebEnvironment getWebEnvironment(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation != null) ? annotation.webEnvironment() : null;
}
protected Class<?>[] getClasses(Class<?> testClass) {
protected Class<?> @Nullable [] getClasses(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation != null) ? annotation.classes() : null;
}
protected String[] getProperties(Class<?> testClass) {
protected String @Nullable [] getProperties(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation != null) ? annotation.properties() : null;
}
protected SpringBootTest getAnnotation(Class<?> testClass) {
protected @Nullable SpringBootTest getAnnotation(Class<?> testClass) {
return TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class);
}

46
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java

@ -29,6 +29,7 @@ import org.assertj.core.api.AbstractThrowableAssert; @@ -29,6 +29,7 @@ import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.MapAssert;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@ -54,14 +55,14 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -54,14 +55,14 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ApplicationContextAssert<C extends ApplicationContext>
extends AbstractAssert<ApplicationContextAssert<C>, C> {
private final Throwable startupFailure;
private final @Nullable Throwable startupFailure;
/**
* Create a new {@link ApplicationContextAssert} instance.
* @param applicationContext the source application context
* @param startupFailure the startup failure or {@code null}
*/
ApplicationContextAssert(C applicationContext, Throwable startupFailure) {
ApplicationContextAssert(C applicationContext, @Nullable Throwable startupFailure) {
super(applicationContext, ApplicationContextAssert.class);
Assert.notNull(applicationContext, "'applicationContext' must not be null");
this.startupFailure = startupFailure;
@ -80,7 +81,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -80,7 +81,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
*/
public ApplicationContextAssert<C> hasBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to have bean named:%n <%s>", name));
throwAssertionError(
contextFailedToStartWhenExpecting(this.startupFailure, "to have bean named:%n <%s>", name));
}
if (findBean(name) == null) {
throwAssertionError(new BasicErrorMessageFactory(
@ -123,7 +125,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -123,7 +125,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
public ApplicationContextAssert<C> hasSingleBean(Class<?> type, Scope scope) {
Assert.notNull(scope, "'scope' must not be null");
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to have a single bean of type:%n <%s>", type));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure,
"to have a single bean of type:%n <%s>", type));
}
String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
if (names.length == 0) {
@ -170,7 +173,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -170,7 +173,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type, Scope scope) {
Assert.notNull(scope, "'scope' must not be null");
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of type:%n <%s>", type));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure,
"not to have any beans of type:%n <%s>", type));
}
String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
if (names.length > 0) {
@ -194,7 +198,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -194,7 +198,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
*/
public ApplicationContextAssert<C> doesNotHaveBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of name:%n <%s>", name));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure,
"not to have any beans of name:%n <%s>", name));
}
try {
Object bean = getApplicationContext().getBean(name);
@ -221,7 +226,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -221,7 +226,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
*/
public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to get beans names with type:%n <%s>", type));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure,
"to get beans names with type:%n <%s>", type));
}
return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type))
.as("Bean names of type <%s> from <%s>", type, getApplicationContext());
@ -267,7 +273,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -267,7 +273,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
public <T> AbstractObjectAssert<?, T> getBean(Class<T> type, Scope scope) {
Assert.notNull(scope, "'scope' must not be null");
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to contain bean of type:%n <%s>", type));
throwAssertionError(
contextFailedToStartWhenExpecting(this.startupFailure, "to contain bean of type:%n <%s>", type));
}
String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
String name = (names.length > 0) ? getPrimary(names, scope) : null;
@ -280,7 +287,7 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -280,7 +287,7 @@ public class ApplicationContextAssert<C extends ApplicationContext>
return Assertions.assertThat(bean).as("Bean of type <%s> from <%s>", type, getApplicationContext());
}
private String getPrimary(String[] names, Scope scope) {
private @Nullable String getPrimary(String[] names, Scope scope) {
if (names.length == 1) {
return names[0];
}
@ -325,7 +332,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -325,7 +332,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
*/
public AbstractObjectAssert<?, Object> getBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s>", name));
throwAssertionError(
contextFailedToStartWhenExpecting(this.startupFailure, "to contain a bean of name:%n <%s>", name));
}
Object bean = findBean(name);
return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name, getApplicationContext());
@ -351,8 +359,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -351,8 +359,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
@SuppressWarnings("unchecked")
public <T> AbstractObjectAssert<?, T> getBean(String name, Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(
contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s> (%s)", name, type));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure,
"to contain a bean of name:%n <%s> (%s)", name, type));
}
Object bean = findBean(name);
if (bean != null && type != null && !type.isInstance(bean)) {
@ -364,7 +372,7 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -364,7 +372,7 @@ public class ApplicationContextAssert<C extends ApplicationContext>
.as("Bean of name <%s> and type <%s> from <%s>", name, type, getApplicationContext());
}
private Object findBean(String name) {
private @Nullable Object findBean(String name) {
try {
return getApplicationContext().getBean(name);
}
@ -409,7 +417,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -409,7 +417,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
public <T> MapAssert<String, T> getBeans(Class<T> type, Scope scope) {
Assert.notNull(scope, "'scope' must not be null");
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to get beans of type:%n <%s>", type));
throwAssertionError(
contextFailedToStartWhenExpecting(this.startupFailure, "to get beans of type:%n <%s>", type));
}
return Assertions.assertThat(scope.getBeansOfType(getApplicationContext(), type))
.as("Beans of type <%s> from <%s>", type, getApplicationContext());
@ -456,7 +465,7 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -456,7 +465,7 @@ public class ApplicationContextAssert<C extends ApplicationContext>
*/
public ApplicationContextAssert<C> hasNotFailed() {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to have not failed"));
throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, "to have not failed"));
}
return this;
}
@ -465,12 +474,13 @@ public class ApplicationContextAssert<C extends ApplicationContext> @@ -465,12 +474,13 @@ public class ApplicationContextAssert<C extends ApplicationContext>
return this.actual;
}
protected final Throwable getStartupFailure() {
protected final @Nullable Throwable getStartupFailure() {
return this.startupFailure;
}
private ContextFailedToStart<C> contextFailedToStartWhenExpecting(String expectationFormat, Object... arguments) {
return new ContextFailedToStart<>(getApplicationContext(), this.startupFailure, expectationFormat, arguments);
private ContextFailedToStart<C> contextFailedToStartWhenExpecting(Throwable startupFailure,
String expectationFormat, Object... arguments) {
return new ContextFailedToStart<>(getApplicationContext(), startupFailure, expectationFormat, arguments);
}
/**

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java

@ -22,6 +22,7 @@ import java.util.Arrays; @@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.function.Supplier;
import org.assertj.core.api.AssertProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
@ -86,7 +87,7 @@ public interface ApplicationContextAssertProvider<C extends ApplicationContext> @@ -86,7 +87,7 @@ public interface ApplicationContextAssertProvider<C extends ApplicationContext>
* context started without issue.
* @return the startup failure or {@code null}
*/
Throwable getStartupFailure();
@Nullable Throwable getStartupFailure();
@Override
void close();

14
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java

@ -24,6 +24,8 @@ import java.lang.reflect.Method; @@ -24,6 +24,8 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
@ -39,9 +41,9 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -39,9 +41,9 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
private final Class<?> applicationContextType;
private final ApplicationContext applicationContext;
private final @Nullable ApplicationContext applicationContext;
private final RuntimeException startupFailure;
private final @Nullable RuntimeException startupFailure;
AssertProviderApplicationContextInvocationHandler(Class<?> applicationContextType, Supplier<?> contextSupplier) {
this.applicationContextType = applicationContextType;
@ -66,7 +68,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -66,7 +68,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isToString(method)) {
return toString();
}
@ -95,6 +97,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -95,6 +97,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
return "Unstarted application context " + this.applicationContextType.getName() + "[startupFailure="
+ this.startupFailure.getClass().getName() + "]";
}
Assert.state(this.applicationContext != null, "'applicationContext' must not be null");
ToStringCreator builder = new ToStringCreator(this.applicationContext)
.append("id", this.applicationContext.getId())
.append("applicationName", this.applicationContext.getApplicationName())
@ -119,7 +122,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -119,7 +122,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
return ("getStartupFailure".equals(method.getName()) && method.getParameterCount() == 0);
}
private Object getStartupFailure() {
private @Nullable Object getStartupFailure() {
return this.startupFailure;
}
@ -135,7 +138,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -135,7 +138,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
return ("close".equals(method.getName()) && method.getParameterCount() == 0);
}
private Object invokeClose() throws IOException {
private @Nullable Object invokeClose() throws IOException {
if (this.applicationContext instanceof Closeable closeable) {
closeable.close();
}
@ -155,6 +158,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan @@ -155,6 +158,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan
if (this.startupFailure != null) {
throw new IllegalStateException(this + " failed to start", this.startupFailure);
}
Assert.state(this.applicationContext != null, "'applicationContext' must not be null");
return this.applicationContext;
}

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* AssertJ support for ApplicationContexts.
*/
@NullMarked
package org.springframework.boot.test.context.assertj;
import org.jspecify.annotations.NullMarked;

4
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java

@ -18,6 +18,8 @@ package org.springframework.boot.test.context.filter; @@ -18,6 +18,8 @@ package org.springframework.boot.test.context.filter;
import java.io.IOException;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.context.TestComponent;
@ -68,7 +70,7 @@ class TestTypeExcludeFilter extends TypeExcludeFilter { @@ -68,7 +70,7 @@ class TestTypeExcludeFilter extends TypeExcludeFilter {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
return (obj != null) && (getClass() == obj.getClass());
}

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Test support for {@link org.springframework.boot.context.TypeExcludeFilter}.
*/
@NullMarked
package org.springframework.boot.test.context.filter;
import org.jspecify.annotations.NullMarked;

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Classes and annotations related to configuring Spring's {@code ApplicationContext} for
* tests.
*/
@NullMarked
package org.springframework.boot.test.context;
import org.jspecify.annotations.NullMarked;

26
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java

@ -24,6 +24,8 @@ import java.util.function.Consumer; @@ -24,6 +24,8 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@ -213,7 +215,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -213,7 +215,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
* @return a new instance with the updated class loader
* @see FilteredClassLoader
*/
public SELF withClassLoader(ClassLoader classLoader) {
public SELF withClassLoader(@Nullable ClassLoader classLoader) {
return newInstance(this.runnerConfiguration.withClassLoader(classLoader));
}
@ -258,7 +260,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -258,7 +260,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
* @param <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(String name, Class<T> type, Object... constructorArgs) {
public <T> SELF withBean(@Nullable String name, Class<T> type, Object... constructorArgs) {
return newInstance(this.runnerConfiguration.withBean(name, type, constructorArgs));
}
@ -295,7 +297,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -295,7 +297,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
* @param <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(String name, Class<T> type, Supplier<T> supplier,
public <T> SELF withBean(@Nullable String name, Class<T> type, Supplier<T> supplier,
BeanDefinitionCustomizer... customizers) {
return newInstance(this.runnerConfiguration.withBean(name, type, supplier, customizers));
}
@ -370,7 +372,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -370,7 +372,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
}
}
private void withContextClassLoader(ClassLoader classLoader, Runnable action) {
private void withContextClassLoader(@Nullable ClassLoader classLoader, Runnable action) {
if (classLoader == null) {
action.run();
}
@ -392,6 +394,8 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -392,6 +394,8 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
ResolvableType resolvableType = ResolvableType.forClass(AbstractApplicationContextRunner.class, getClass());
Class<A> assertType = (Class<A>) resolvableType.resolveGeneric(1);
Class<C> contextType = (Class<C>) resolvableType.resolveGeneric(2);
Assert.state(assertType != null, "'assertType' must not be null");
Assert.state(contextType != null, "'contextType' must not be null");
return ApplicationContextAssertProvider.get(assertType, contextType, () -> createAndLoadContext(refresh),
this.runnerConfiguration.additionalContextInterfaces);
}
@ -471,11 +475,11 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -471,11 +475,11 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
Consumer<GenericApplicationContext> registrar;
public BeanRegistration(String name, Class<T> type, Object... constructorArgs) {
public BeanRegistration(@Nullable String name, Class<T> type, Object... constructorArgs) {
this.registrar = (context) -> context.registerBean(name, type, constructorArgs);
}
public BeanRegistration(String name, Class<T> type, Supplier<T> supplier,
public BeanRegistration(@Nullable String name, Class<T> type, Supplier<T> supplier,
BeanDefinitionCustomizer... customizers) {
this.registrar = (context) -> context.registerBean(name, type, supplier, customizers);
}
@ -503,9 +507,9 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -503,9 +507,9 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
private TestPropertyValues systemProperties = TestPropertyValues.empty();
private ClassLoader classLoader;
private @Nullable ClassLoader classLoader;
private ApplicationContext parent;
private @Nullable ApplicationContext parent;
private List<BeanRegistration<?>> beanRegistrations = Collections.emptyList();
@ -561,7 +565,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -561,7 +565,7 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
return config;
}
private RunnerConfiguration<C> withClassLoader(ClassLoader classLoader) {
private RunnerConfiguration<C> withClassLoader(@Nullable ClassLoader classLoader) {
RunnerConfiguration<C> config = new RunnerConfiguration<>(this);
config.classLoader = classLoader;
return config;
@ -573,14 +577,14 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl @@ -573,14 +577,14 @@ public abstract class AbstractApplicationContextRunner<SELF extends AbstractAppl
return config;
}
private <T> RunnerConfiguration<C> withBean(String name, Class<T> type, Object... constructorArgs) {
private <T> RunnerConfiguration<C> withBean(@Nullable String name, Class<T> type, Object... constructorArgs) {
RunnerConfiguration<C> config = new RunnerConfiguration<>(this);
config.beanRegistrations = add(config.beanRegistrations,
new BeanRegistration<>(name, type, constructorArgs));
return config;
}
private <T> RunnerConfiguration<C> withBean(String name, Class<T> type, Supplier<T> supplier,
private <T> RunnerConfiguration<C> withBean(@Nullable String name, Class<T> type, Supplier<T> supplier,
BeanDefinitionCustomizer... customizers) {
RunnerConfiguration<C> config = new RunnerConfiguration<>(this);
config.beanRegistrations = add(config.beanRegistrations,

8
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java

@ -18,8 +18,11 @@ package org.springframework.boot.test.context.runner; @@ -18,8 +18,11 @@ package org.springframework.boot.test.context.runner;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext;
import org.springframework.lang.Contract;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
@ -81,8 +84,9 @@ public final class WebApplicationContextRunner extends @@ -81,8 +84,9 @@ public final class WebApplicationContextRunner extends
* @param contextFactory the context factory to decorate
* @return an updated supplier that will set the {@link MockServletContext}
*/
public static Supplier<ConfigurableWebApplicationContext> withMockServletContext(
Supplier<ConfigurableWebApplicationContext> contextFactory) {
@Contract("!null -> !null")
public static @Nullable Supplier<ConfigurableWebApplicationContext> withMockServletContext(
@Nullable Supplier<ConfigurableWebApplicationContext> contextFactory) {
return (contextFactory != null) ? () -> {
ConfigurableWebApplicationContext context = contextFactory.get();
context.setServletContext(new MockServletContext());

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Test utilities to run application contexts for testing.
*/
@NullMarked
package org.springframework.boot.test.context.runner;
import org.jspecify.annotations.NullMarked;

29
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java

@ -27,6 +27,7 @@ import java.io.StringReader; @@ -27,6 +27,7 @@ import java.io.StringReader;
import java.lang.reflect.Field;
import org.assertj.core.api.Assertions;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
@ -68,9 +69,9 @@ import org.springframework.util.ReflectionUtils; @@ -68,9 +69,9 @@ import org.springframework.util.ReflectionUtils;
*/
public abstract class AbstractJsonMarshalTester<T> {
private Class<?> resourceLoadClass;
private @Nullable Class<?> resourceLoadClass;
private ResolvableType type;
private @Nullable ResolvableType type;
/**
* Create a new uninitialized {@link AbstractJsonMarshalTester} instance.
@ -107,18 +108,30 @@ public abstract class AbstractJsonMarshalTester<T> { @@ -107,18 +108,30 @@ public abstract class AbstractJsonMarshalTester<T> {
* Return the type under test.
* @return the type under test
*/
protected final ResolvableType getType() {
protected final @Nullable ResolvableType getType() {
return this.type;
}
private ResolvableType getTypeNotNull() {
ResolvableType type = getType();
Assert.state(type != null, "Instance has not been initialized");
return type;
}
/**
* Return class used to load relative resources.
* @return the resource load class
*/
protected final Class<?> getResourceLoadClass() {
protected final @Nullable Class<?> getResourceLoadClass() {
return this.resourceLoadClass;
}
private Class<?> getResourceLoadClassNotNull() {
Class<?> resourceLoadClass = getResourceLoadClass();
Assert.state(resourceLoadClass != null, "Instance has not been initialized");
return resourceLoadClass;
}
/**
* Return {@link JsonContent} from writing the specific value.
* @param value the value to write
@ -128,7 +141,7 @@ public abstract class AbstractJsonMarshalTester<T> { @@ -128,7 +141,7 @@ public abstract class AbstractJsonMarshalTester<T> {
public JsonContent<T> write(T value) throws IOException {
verify();
Assert.notNull(value, "'value' must not be null");
String json = writeObject(value, this.type);
String json = writeObject(value, getTypeNotNull());
return getJsonContent(json);
}
@ -140,7 +153,7 @@ public abstract class AbstractJsonMarshalTester<T> { @@ -140,7 +153,7 @@ public abstract class AbstractJsonMarshalTester<T> {
* @since 2.1.5
*/
protected JsonContent<T> getJsonContent(String json) {
return new JsonContent<>(getResourceLoadClass(), getType(), json);
return new JsonContent<>(getResourceLoadClassNotNull(), getType(), json);
}
/**
@ -281,7 +294,7 @@ public abstract class AbstractJsonMarshalTester<T> { @@ -281,7 +294,7 @@ public abstract class AbstractJsonMarshalTester<T> {
verify();
Assert.notNull(resource, "'resource' must not be null");
InputStream inputStream = resource.getInputStream();
T object = readObject(inputStream, this.type);
T object = readObject(inputStream, getTypeNotNull());
closeQuietly(inputStream);
return new ObjectContent<>(this.type, object);
}
@ -306,7 +319,7 @@ public abstract class AbstractJsonMarshalTester<T> { @@ -306,7 +319,7 @@ public abstract class AbstractJsonMarshalTester<T> {
public ObjectContent<T> read(Reader reader) throws IOException {
verify();
Assert.notNull(reader, "'reader' must not be null");
T object = readObject(reader, this.type);
T object = readObject(reader, getTypeNotNull());
closeQuietly(reader);
return new ObjectContent<>(this.type, object);
}

39
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java

@ -20,6 +20,8 @@ import java.io.File; @@ -20,6 +20,8 @@ import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
@ -47,7 +49,7 @@ import org.springframework.util.Assert; @@ -47,7 +49,7 @@ import org.springframework.util.Assert;
*/
public class BasicJsonTester {
private JsonLoader loader;
private @Nullable JsonLoader loader;
/**
* Create a new uninitialized {@link BasicJsonTester} instance.
@ -69,7 +71,7 @@ public class BasicJsonTester { @@ -69,7 +71,7 @@ public class BasicJsonTester {
* @param charset the charset used to load resources
* @since 1.4.1
*/
public BasicJsonTester(Class<?> resourceLoadClass, Charset charset) {
public BasicJsonTester(Class<?> resourceLoadClass, @Nullable Charset charset) {
Assert.notNull(resourceLoadClass, "'resourceLoadClass' must not be null");
this.loader = new JsonLoader(resourceLoadClass, charset);
}
@ -91,7 +93,7 @@ public class BasicJsonTester { @@ -91,7 +93,7 @@ public class BasicJsonTester {
* @param charset the charset used when loading relative classpath resources
* @since 1.4.1
*/
protected final void initialize(Class<?> resourceLoadClass, Charset charset) {
protected final void initialize(Class<?> resourceLoadClass, @Nullable Charset charset) {
if (this.loader == null) {
this.loader = new JsonLoader(resourceLoadClass, charset);
}
@ -105,8 +107,8 @@ public class BasicJsonTester { @@ -105,8 +107,8 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(CharSequence source) {
verify();
return getJsonContent(this.loader.getJson(source));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(source));
}
/**
@ -116,8 +118,8 @@ public class BasicJsonTester { @@ -116,8 +118,8 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(String path, Class<?> resourceLoadClass) {
verify();
return getJsonContent(this.loader.getJson(path, resourceLoadClass));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(path, resourceLoadClass));
}
/**
@ -126,8 +128,8 @@ public class BasicJsonTester { @@ -126,8 +128,8 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(byte[] source) {
verify();
return getJsonContent(this.loader.getJson(source));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(source));
}
/**
@ -136,8 +138,8 @@ public class BasicJsonTester { @@ -136,8 +138,8 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(File source) {
verify();
return getJsonContent(this.loader.getJson(source));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(source));
}
/**
@ -146,8 +148,8 @@ public class BasicJsonTester { @@ -146,8 +148,8 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(InputStream source) {
verify();
return getJsonContent(this.loader.getJson(source));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(source));
}
/**
@ -156,16 +158,17 @@ public class BasicJsonTester { @@ -156,16 +158,17 @@ public class BasicJsonTester {
* @return the JSON content
*/
public JsonContent<Object> from(Resource source) {
verify();
return getJsonContent(this.loader.getJson(source));
JsonLoader loader = verify();
return getJsonContent(loader, loader.getJson(source));
}
private void verify() {
private JsonLoader verify() {
Assert.state(this.loader != null, "Uninitialized BasicJsonTester");
return this.loader;
}
private JsonContent<Object> getJsonContent(String json) {
return new JsonContent<>(this.loader.getResourceLoadClass(), null, json);
private JsonContent<Object> getJsonContent(JsonLoader loader, String json) {
return new JsonContent<>(loader.getResourceLoadClass(), null, json);
}
}

16
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java

@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectWriter; @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectWriter;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
@ -66,7 +67,7 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { @@ -66,7 +67,7 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper;
private Class<?> view;
private @Nullable Class<?> view;
/**
* Create a new {@link JacksonTester} instance.
@ -87,7 +88,8 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { @@ -87,7 +88,8 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
this(resourceLoadClass, type, objectMapper, null);
}
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, Class<?> view) {
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
@Nullable Class<?> view) {
super(resourceLoadClass, type);
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
@ -100,7 +102,9 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { @@ -100,7 +102,9 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
.jsonProvider(new JacksonJsonProvider(this.objectMapper))
.mappingProvider(new JacksonMappingProvider(this.objectMapper))
.build();
return new JsonContent<>(getResourceLoadClass(), getType(), json, configuration);
Class<?> resourceLoadClass = getResourceLoadClass();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
return new JsonContent<>(resourceLoadClass, getType(), json, configuration);
}
@Override
@ -167,7 +171,11 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> { @@ -167,7 +171,11 @@ public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
* @return the new instance
*/
public JacksonTester<T> forView(Class<?> view) {
return new JacksonTester<>(getResourceLoadClass(), getType(), this.objectMapper, view);
Class<?> resourceLoadClass = getResourceLoadClass();
ResolvableType type = getType();
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
Assert.state(type != null, "'type' must not be null");
return new JacksonTester<>(resourceLoadClass, type, this.objectMapper, view);
}
/**

7
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java

@ -18,6 +18,7 @@ package org.springframework.boot.test.json; @@ -18,6 +18,7 @@ package org.springframework.boot.test.json;
import com.jayway.jsonpath.Configuration;
import org.assertj.core.api.AssertProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
@ -36,7 +37,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> { @@ -36,7 +37,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> {
private final Class<?> resourceLoadClass;
private final ResolvableType type;
private final @Nullable ResolvableType type;
private final String json;
@ -48,7 +49,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> { @@ -48,7 +49,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> {
* @param type the type under test (or {@code null} if not known)
* @param json the actual JSON content
*/
public JsonContent(Class<?> resourceLoadClass, ResolvableType type, String json) {
public JsonContent(Class<?> resourceLoadClass, @Nullable ResolvableType type, String json) {
this(resourceLoadClass, type, json, Configuration.defaultConfiguration());
}
@ -59,7 +60,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> { @@ -59,7 +60,7 @@ public final class JsonContent<T> implements AssertProvider<JsonContentAssert> {
* @param json the actual JSON content
* @param configuration the JsonPath configuration
*/
JsonContent(Class<?> resourceLoadClass, ResolvableType type, String json, Configuration configuration) {
JsonContent(Class<?> resourceLoadClass, @Nullable ResolvableType type, String json, Configuration configuration) {
Assert.notNull(resourceLoadClass, "'resourceLoadClass' must not be null");
Assert.notNull(json, "'json' must not be null");
Assert.notNull(configuration, "'configuration' must not be null");

30
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java

@ -33,6 +33,7 @@ import org.assertj.core.api.Assert; @@ -33,6 +33,7 @@ import org.assertj.core.api.Assert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.ListAssert;
import org.assertj.core.api.MapAssert;
import org.jspecify.annotations.Nullable;
import org.skyscreamer.jsonassert.JSONCompare;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult;
@ -74,7 +75,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -74,7 +75,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @param json the actual JSON content
* @since 1.4.1
*/
public JsonContentAssert(Class<?> resourceLoadClass, Charset charset, CharSequence json) {
public JsonContentAssert(Class<?> resourceLoadClass, @Nullable Charset charset, CharSequence json) {
this(resourceLoadClass, charset, json, Configuration.defaultConfiguration());
}
@ -86,7 +87,8 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -86,7 +87,8 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @param json the actual JSON content
* @param configuration the json-path configuration
*/
JsonContentAssert(Class<?> resourceLoadClass, Charset charset, CharSequence json, Configuration configuration) {
JsonContentAssert(Class<?> resourceLoadClass, @Nullable Charset charset, CharSequence json,
Configuration configuration) {
super(json, JsonContentAssert.class);
this.configuration = configuration;
this.loader = new JsonLoader(resourceLoadClass, charset);
@ -98,7 +100,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -98,7 +100,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @see org.assertj.core.api.AbstractAssert#isEqualTo(java.lang.Object)
*/
@Override
public JsonContentAssert isEqualTo(Object expected) {
public JsonContentAssert isEqualTo(@Nullable Object expected) {
if (expected == null || expected instanceof CharSequence) {
return isEqualToJson((CharSequence) expected);
}
@ -115,7 +117,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -115,7 +117,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
return isEqualToJson(resource);
}
failWithMessage("Unsupported type for JSON assert %s", expected.getClass());
return null;
return this;
}
/**
@ -128,7 +130,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -128,7 +130,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @return {@code this} assertion object
* @throws AssertionError if the actual JSON value is not equal to the given one
*/
public JsonContentAssert isEqualToJson(CharSequence expected) {
public JsonContentAssert isEqualToJson(@Nullable CharSequence expected) {
String expectedJson = this.loader.getJson(expected);
return assertNotFailed(compare(expectedJson, JSONCompareMode.LENIENT));
}
@ -426,7 +428,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -426,7 +428,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @see org.assertj.core.api.AbstractAssert#isEqualTo(java.lang.Object)
*/
@Override
public JsonContentAssert isNotEqualTo(Object expected) {
public JsonContentAssert isNotEqualTo(@Nullable Object expected) {
if (expected == null || expected instanceof CharSequence) {
return isNotEqualToJson((CharSequence) expected);
}
@ -443,7 +445,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -443,7 +445,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
return isNotEqualToJson(resource);
}
failWithMessage("Unsupported type for JSON assert %s", expected.getClass());
return null;
return this;
}
/**
@ -456,7 +458,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -456,7 +458,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
* @return {@code this} assertion object
* @throws AssertionError if the actual JSON value is equal to the given one
*/
public JsonContentAssert isNotEqualToJson(CharSequence expected) {
public JsonContentAssert isNotEqualToJson(@Nullable CharSequence expected) {
String expectedJson = this.loader.getJson(expected);
return assertNotPassed(compare(expectedJson, JSONCompareMode.LENIENT));
}
@ -985,7 +987,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -985,7 +987,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
}
@SuppressWarnings("unchecked")
private <T> T extractingJsonPathValue(CharSequence expression, Object[] args, Class<T> type,
private <T> @Nullable T extractingJsonPathValue(CharSequence expression, Object[] args, Class<T> type,
String expectedDescription) {
JsonPathValue value = new JsonPathValue(expression, args);
if (value.getValue(false) != null) {
@ -994,7 +996,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -994,7 +996,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
return (T) value.getValue(false);
}
private JSONCompareResult compare(CharSequence expectedJson, JSONCompareMode compareMode) {
private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONCompareMode compareMode) {
if (this.actual == null) {
return compareForNull(expectedJson);
}
@ -1010,7 +1012,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -1010,7 +1012,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
}
}
private JSONCompareResult compare(CharSequence expectedJson, JSONComparator comparator) {
private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONComparator comparator) {
if (this.actual == null) {
return compareForNull(expectedJson);
}
@ -1026,7 +1028,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -1026,7 +1028,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
}
}
private JSONCompareResult compareForNull(CharSequence expectedJson) {
private JSONCompareResult compareForNull(@Nullable CharSequence expectedJson) {
JSONCompareResult result = new JSONCompareResult();
result.passed();
if (expectedJson != null) {
@ -1099,7 +1101,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -1099,7 +1101,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
}
}
void assertHasValue(Class<?> type, String expectedDescription) {
void assertHasValue(@Nullable Class<?> type, String expectedDescription) {
Object value = getValue(true);
if (value == null || isIndefiniteAndEmpty()) {
failWithNoValueMessage();
@ -1128,7 +1130,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq @@ -1128,7 +1130,7 @@ public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSeq
return ObjectUtils.isEmpty(getValue(false));
}
Object getValue(boolean required) {
@Nullable Object getValue(boolean required) {
try {
return read();
}

8
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonLoader.java

@ -25,8 +25,11 @@ import java.io.InputStreamReader; @@ -25,8 +25,11 @@ import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Contract;
import org.springframework.util.FileCopyUtils;
/**
@ -41,7 +44,7 @@ class JsonLoader { @@ -41,7 +44,7 @@ class JsonLoader {
private final Charset charset;
JsonLoader(Class<?> resourceLoadClass, Charset charset) {
JsonLoader(Class<?> resourceLoadClass, @Nullable Charset charset) {
this.resourceLoadClass = resourceLoadClass;
this.charset = (charset != null) ? charset : StandardCharsets.UTF_8;
}
@ -50,7 +53,8 @@ class JsonLoader { @@ -50,7 +53,8 @@ class JsonLoader {
return this.resourceLoadClass;
}
String getJson(CharSequence source) {
@Contract("!null -> !null")
@Nullable String getJson(@Nullable CharSequence source) {
if (source == null) {
return null;
}

5
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.boot.test.json;
import org.assertj.core.api.AssertProvider;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
@ -32,7 +33,7 @@ import org.springframework.util.Assert; @@ -32,7 +33,7 @@ import org.springframework.util.Assert;
*/
public final class ObjectContent<T> implements AssertProvider<ObjectContentAssert<T>> {
private final ResolvableType type;
private final @Nullable ResolvableType type;
private final T object;
@ -41,7 +42,7 @@ public final class ObjectContent<T> implements AssertProvider<ObjectContentAsser @@ -41,7 +42,7 @@ public final class ObjectContent<T> implements AssertProvider<ObjectContentAsser
* @param type the type under test (or {@code null} if not known)
* @param object the actual object content
*/
public ObjectContent(ResolvableType type, T object) {
public ObjectContent(@Nullable ResolvableType type, T object) {
Assert.notNull(object, "'object' must not be null");
this.type = type;
this.object = object;

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/json/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Support for testing JSON.
*/
@NullMarked
package org.springframework.boot.test.json;
import org.jspecify.annotations.NullMarked;

6
core/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/SpringBootMockServletContext.java

@ -22,6 +22,8 @@ import java.net.MalformedURLException; @@ -22,6 +22,8 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@ -42,7 +44,7 @@ public class SpringBootMockServletContext extends MockServletContext { @@ -42,7 +44,7 @@ public class SpringBootMockServletContext extends MockServletContext {
private final ResourceLoader resourceLoader;
private File emptyRootDirectory;
private @Nullable File emptyRootDirectory;
public SpringBootMockServletContext(String resourceBasePath) {
this(resourceBasePath, new FileSystemResourceLoader());
@ -86,7 +88,7 @@ public class SpringBootMockServletContext extends MockServletContext { @@ -86,7 +88,7 @@ public class SpringBootMockServletContext extends MockServletContext {
}
@Override
public URL getResource(String path) throws MalformedURLException {
public @Nullable URL getResource(String path) throws MalformedURLException {
URL resource = super.getResource(path);
if (resource == null && "/".equals(path)) {
// Liquibase assumes that "/" always exists, if we don't have a directory

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/mock/web/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Mock web classes specific to Spring Boot.
*/
@NullMarked
package org.springframework.boot.test.mock.web;
import org.jspecify.annotations.NullMarked;

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/rsocket/server/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* RSocket server test utilities and support classes.
*/
@NullMarked
package org.springframework.boot.test.rsocket.server;
import org.jspecify.annotations.NullMarked;

6
core/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java

@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference; @@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.util.Assert;
@ -47,7 +49,7 @@ class OutputCapture implements CapturedOutput { @@ -47,7 +49,7 @@ class OutputCapture implements CapturedOutput {
private final Deque<SystemCapture> systemCaptures = new ArrayDeque<>();
private AnsiOutputState ansiOutputState;
private @Nullable AnsiOutputState ansiOutputState;
private final AtomicReference<String> out = new AtomicReference<>(null);
@ -329,7 +331,7 @@ class OutputCapture implements CapturedOutput { @@ -329,7 +331,7 @@ class OutputCapture implements CapturedOutput {
AnsiOutput.setEnabled(this.saved);
}
static AnsiOutputState saveAndDisable() {
static @Nullable AnsiOutputState saveAndDisable() {
if (!ClassUtils.isPresent("org.springframework.boot.ansi.AnsiOutput",
OutputCapture.class.getClassLoader())) {
return null;

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Classes for {@link java.lang.System System}-related testing.
*/
@NullMarked
package org.springframework.boot.test.system;
import org.jspecify.annotations.NullMarked;

4
core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.boot.test.util;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@ -31,7 +33,7 @@ public abstract class ApplicationContextTestUtils { @@ -31,7 +33,7 @@ public abstract class ApplicationContextTestUtils {
* Closes this {@link ApplicationContext} and its parent hierarchy if any.
* @param context the context to close (can be {@code null})
*/
public static void closeAll(ApplicationContext context) {
public static void closeAll(@Nullable ApplicationContext context) {
if (context != null) {
if (context instanceof ConfigurableApplicationContext configurableContext) {
configurableContext.close();

40
core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java

@ -27,6 +27,8 @@ import java.util.function.Function; @@ -27,6 +27,8 @@ import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@ -37,6 +39,7 @@ import org.springframework.core.env.MutablePropertySources; @@ -37,6 +39,7 @@ import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -76,7 +79,7 @@ public final class TestPropertyValues { @@ -76,7 +79,7 @@ public final class TestPropertyValues {
* @return a new {@link TestPropertyValues} instance
* @since 2.4.0
*/
public TestPropertyValues and(Iterable<String> pairs) {
public TestPropertyValues and(@Nullable Iterable<String> pairs) {
return (pairs != null) ? and(StreamSupport.stream(pairs.spliterator(), false)) : this;
}
@ -87,7 +90,7 @@ public final class TestPropertyValues { @@ -87,7 +90,7 @@ public final class TestPropertyValues {
* @return a new {@link TestPropertyValues} instance
* @since 2.4.0
*/
public TestPropertyValues and(Stream<String> pairs) {
public TestPropertyValues and(@Nullable Stream<String> pairs) {
return (pairs != null) ? and(pairs, Pair::parse) : this;
}
@ -97,7 +100,7 @@ public final class TestPropertyValues { @@ -97,7 +100,7 @@ public final class TestPropertyValues {
* @return a new {@link TestPropertyValues} instance
* @since 2.4.0
*/
public TestPropertyValues and(Map<String, String> map) {
public TestPropertyValues and(@Nullable Map<String, String> map) {
return (map != null) ? and(map.entrySet().stream(), Pair::fromMapEntry) : this;
}
@ -110,7 +113,7 @@ public final class TestPropertyValues { @@ -110,7 +113,7 @@ public final class TestPropertyValues {
* @return a new {@link TestPropertyValues} instance
* @since 2.4.0
*/
public <T> TestPropertyValues and(Stream<T> stream, Function<T, Pair> mapper) {
public <T> TestPropertyValues and(@Nullable Stream<T> stream, Function<T, @Nullable Pair> mapper) {
if (stream == null) {
return this;
}
@ -200,8 +203,8 @@ public final class TestPropertyValues { @@ -200,8 +203,8 @@ public final class TestPropertyValues {
@SuppressWarnings("unchecked")
private void addToSources(MutablePropertySources sources, Type type, String name) {
if (sources.contains(name)) {
PropertySource<?> propertySource = sources.get(name);
PropertySource<?> propertySource = sources.get(name);
if (propertySource != null) {
if (propertySource.getClass() == type.getSourceClass()) {
((Map<String, Object>) propertySource.getSource()).putAll(this.properties);
return;
@ -232,7 +235,7 @@ public final class TestPropertyValues { @@ -232,7 +235,7 @@ public final class TestPropertyValues {
* environment
* @return the new instance
*/
public static TestPropertyValues of(Iterable<String> pairs) {
public static TestPropertyValues of(@Nullable Iterable<String> pairs) {
return (pairs != null) ? of(StreamSupport.stream(pairs.spliterator(), false)) : empty();
}
@ -244,7 +247,7 @@ public final class TestPropertyValues { @@ -244,7 +247,7 @@ public final class TestPropertyValues {
* environment
* @return the new instance
*/
public static TestPropertyValues of(Stream<String> pairs) {
public static TestPropertyValues of(@Nullable Stream<String> pairs) {
return (pairs != null) ? of(pairs, Pair::parse) : empty();
}
@ -254,7 +257,7 @@ public final class TestPropertyValues { @@ -254,7 +257,7 @@ public final class TestPropertyValues {
* @param map the map of properties that need to be added to the environment
* @return the new instance
*/
public static TestPropertyValues of(Map<String, String> map) {
public static TestPropertyValues of(@Nullable Map<String, String> map) {
return (map != null) ? of(map.entrySet().stream(), Pair::fromMapEntry) : empty();
}
@ -267,7 +270,7 @@ public final class TestPropertyValues { @@ -267,7 +270,7 @@ public final class TestPropertyValues {
* {@link Pair}
* @return the new instance
*/
public static <T> TestPropertyValues of(Stream<T> stream, Function<T, Pair> mapper) {
public static <T> TestPropertyValues of(@Nullable Stream<T> stream, Function<T, @Nullable Pair> mapper) {
return (stream != null) ? empty().and(stream, mapper) : empty();
}
@ -297,9 +300,9 @@ public final class TestPropertyValues { @@ -297,9 +300,9 @@ public final class TestPropertyValues {
private final Class<? extends MapPropertySource> sourceClass;
private final String suffix;
private final @Nullable String suffix;
Type(Class<? extends MapPropertySource> sourceClass, String suffix) {
Type(Class<? extends MapPropertySource> sourceClass, @Nullable String suffix) {
this.sourceClass = sourceClass;
this.suffix = suffix;
}
@ -321,9 +324,9 @@ public final class TestPropertyValues { @@ -321,9 +324,9 @@ public final class TestPropertyValues {
private final String name;
private final String value;
private final @Nullable String value;
private Pair(String name, String value) {
private Pair(String name, @Nullable String value) {
Assert.hasLength(name, "'name' must not be empty");
this.name = name;
this.value = value;
@ -333,7 +336,7 @@ public final class TestPropertyValues { @@ -333,7 +336,7 @@ public final class TestPropertyValues {
properties.put(this.name, this.value);
}
public static Pair parse(String pair) {
public static @Nullable Pair parse(String pair) {
int index = getSeparatorIndex(pair);
String name = (index > 0) ? pair.substring(0, index) : pair;
String value = (index > 0) ? pair.substring(index + 1) : "";
@ -358,7 +361,8 @@ public final class TestPropertyValues { @@ -358,7 +361,8 @@ public final class TestPropertyValues {
* @return the {@link Pair} instance or {@code null}
* @since 2.4.0
*/
public static Pair fromMapEntry(Map.Entry<String, String> entry) {
@Contract("!null -> !null")
public static @Nullable Pair fromMapEntry(Map.@Nullable Entry<String, String> entry) {
return (entry != null) ? of(entry.getKey(), entry.getValue()) : null;
}
@ -369,8 +373,8 @@ public final class TestPropertyValues { @@ -369,8 +373,8 @@ public final class TestPropertyValues {
* @return the {@link Pair} instance or {@code null}
* @since 2.4.0
*/
public static Pair of(String name, String value) {
if (StringUtils.hasLength(name) || StringUtils.hasLength(value)) {
public static @Nullable Pair of(@Nullable String name, @Nullable String value) {
if (StringUtils.hasLength(name)) {
return new Pair(name, value);
}
return null;

3
core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* General purpose test utilities.
*/
@NullMarked
package org.springframework.boot.test.util;
import org.jspecify.annotations.NullMarked;

Loading…
Cancel
Save