Merge branch '3.3.x' into 3.4.x
This commit is contained in:
commit
bfd4e7b4ee
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.testsupport.classpath.resources;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* A resource that is to be made available in tests.
|
||||
*
|
||||
* @param path the path of the resoure
|
||||
* @param additional whether the resource should be made available in addition to those
|
||||
* that already exist elsewhere
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
record Resource(Path path, boolean additional) {
|
||||
|
||||
}
|
|
@ -26,7 +26,9 @@ import java.nio.file.Paths;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -41,6 +43,8 @@ class Resources {
|
|||
|
||||
private final Path root;
|
||||
|
||||
private final Map<String, Resource> resources = new HashMap<>();
|
||||
|
||||
Resources(Path root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
@ -60,6 +64,7 @@ class Resources {
|
|||
Files.createDirectories(targetDirectory);
|
||||
}
|
||||
Files.copy(resource, target);
|
||||
register(resourceName, target, true);
|
||||
unmatchedNames.remove(resourceName);
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +81,7 @@ class Resources {
|
|||
return this;
|
||||
}
|
||||
|
||||
Resources addResource(String name, String content) {
|
||||
Resources addResource(String name, String content, boolean additional) {
|
||||
Path resourcePath = this.root.resolve(name);
|
||||
if (Files.isDirectory(resourcePath)) {
|
||||
throw new IllegalStateException(
|
||||
|
@ -88,6 +93,7 @@ class Resources {
|
|||
Files.createDirectories(parent);
|
||||
}
|
||||
Files.writeString(resourcePath, processContent(content));
|
||||
register(name, resourcePath, additional);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
|
@ -95,6 +101,29 @@ class Resources {
|
|||
return this;
|
||||
}
|
||||
|
||||
private void register(String name, Path resourcePath, boolean additional) {
|
||||
Resource resource = new Resource(resourcePath, additional);
|
||||
register(name, resource);
|
||||
Path ancestor = resourcePath.getParent();
|
||||
while (!this.root.equals(ancestor)) {
|
||||
Resource ancestorResource = new Resource(ancestor, additional);
|
||||
register(this.root.relativize(ancestor).toString(), ancestorResource);
|
||||
ancestor = ancestor.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void register(String name, Resource resource) {
|
||||
this.resources.put(name, resource);
|
||||
if (Files.isDirectory(resource.path())) {
|
||||
if (name.endsWith("/")) {
|
||||
this.resources.put(name.substring(0, name.length() - 1), resource);
|
||||
}
|
||||
else {
|
||||
this.resources.put(name + "/", resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String processContent(String content) {
|
||||
return content.replace("${resourceRoot}", this.root.toString());
|
||||
}
|
||||
|
@ -107,6 +136,7 @@ class Resources {
|
|||
}
|
||||
try {
|
||||
Files.createDirectories(directoryPath);
|
||||
register(name, directoryPath, true);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
|
@ -117,6 +147,7 @@ class Resources {
|
|||
void delete() {
|
||||
try {
|
||||
FileSystemUtils.deleteRecursively(this.root);
|
||||
this.resources.clear();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
|
@ -127,4 +158,8 @@ class Resources {
|
|||
return this.root;
|
||||
}
|
||||
|
||||
Resource find(String name) {
|
||||
return this.resources.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,11 +19,9 @@ package org.springframework.boot.testsupport.classpath.resources;
|
|||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ClassLoader} that provides access to {@link Resources resources}.
|
||||
|
@ -40,23 +38,32 @@ class ResourcesClassLoader extends ClassLoader {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected URL findResource(String name) {
|
||||
Path resource = this.resources.getRoot().resolve(name);
|
||||
if (Files.exists(resource)) {
|
||||
try {
|
||||
return resource.toUri().toURL();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
public URL getResource(String name) {
|
||||
Resource resource = this.resources.find(name);
|
||||
return (resource != null) ? urlOf(resource) : getParent().getResource(name);
|
||||
}
|
||||
|
||||
private URL urlOf(Resource resource) {
|
||||
try {
|
||||
return resource.path().toUri().toURL();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Enumeration<URL> findResources(String name) throws IOException {
|
||||
URL resourceUrl = findResource(name);
|
||||
return (resourceUrl != null) ? Collections.enumeration(List.of(resourceUrl)) : Collections.emptyEnumeration();
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
Resource resource = this.resources.find(name);
|
||||
ArrayList<URL> urls = new ArrayList<>();
|
||||
if (resource != null) {
|
||||
URL resourceUrl = urlOf(resource);
|
||||
urls.add(resourceUrl);
|
||||
}
|
||||
if (resource == null || resource.additional()) {
|
||||
urls.addAll(Collections.list(getParent().getResources(name)));
|
||||
}
|
||||
return Collections.enumeration(urls);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,7 +63,8 @@ class ResourcesExtension implements BeforeEachCallback, AfterEachCallback, Param
|
|||
Resources resources = new Resources(Files.createTempDirectory("resources"));
|
||||
store.put(RESOURCES_KEY, resources);
|
||||
Method testMethod = context.getRequiredTestMethod();
|
||||
resourcesOf(testMethod).forEach((resource) -> resources.addResource(resource.name(), resource.content()));
|
||||
resourcesOf(testMethod)
|
||||
.forEach((resource) -> resources.addResource(resource.name(), resource.content(), resource.additional()));
|
||||
resourceDirectoriesOf(testMethod).forEach((directory) -> resources.addDirectory(directory.value()));
|
||||
packageResourcesOf(testMethod).forEach((withPackageResources) -> resources
|
||||
.addPackage(testMethod.getDeclaringClass().getPackage(), withPackageResources.value()));
|
||||
|
@ -148,8 +149,7 @@ class ResourcesExtension implements BeforeEachCallback, AfterEachCallback, Param
|
|||
private Object resolveResourcePath(ParameterContext parameterContext, ExtensionContext extensionContext) {
|
||||
Resources resources = getResources(extensionContext);
|
||||
Class<?> parameterType = parameterContext.getParameter().getType();
|
||||
Path resourcePath = resources.getRoot()
|
||||
.resolve(parameterContext.findAnnotation(ResourcePath.class).get().value());
|
||||
Path resourcePath = resources.find(parameterContext.findAnnotation(ResourcePath.class).get().value()).path();
|
||||
if (parameterType.isAssignableFrom(Path.class)) {
|
||||
return resourcePath;
|
||||
}
|
||||
|
@ -166,8 +166,7 @@ class ResourcesExtension implements BeforeEachCallback, AfterEachCallback, Param
|
|||
private Object resolveResourceContent(ParameterContext parameterContext, ExtensionContext extensionContext) {
|
||||
Resources resources = getResources(extensionContext);
|
||||
Class<?> parameterType = parameterContext.getParameter().getType();
|
||||
Path resourcePath = resources.getRoot()
|
||||
.resolve(parameterContext.findAnnotation(ResourceContent.class).get().value());
|
||||
Path resourcePath = resources.find(parameterContext.findAnnotation(ResourceContent.class).get().value()).path();
|
||||
if (parameterType.isAssignableFrom(String.class)) {
|
||||
try (InputStream in = Files.newInputStream(resourcePath)) {
|
||||
return StreamUtils.copyToString(in, StandardCharsets.UTF_8);
|
||||
|
|
|
@ -54,4 +54,11 @@ public @interface WithResource {
|
|||
*/
|
||||
String content() default "";
|
||||
|
||||
/**
|
||||
* Whether the resource should be available in addition to those that are already on
|
||||
* the classpath are instead of any existing resources with the same name.
|
||||
* @return whether this is an additional resource
|
||||
*/
|
||||
boolean additional() default true;
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.testsupport.classpath.resources;
|
|||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
|
@ -34,99 +35,122 @@ class ResourcesTests {
|
|||
@TempDir
|
||||
private Path root;
|
||||
|
||||
private Resources resources;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.resources = new Resources(this.root);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceThenResourceIsCreated() {
|
||||
new Resources(this.root).addResource("test", "test-content");
|
||||
void whenAddResourceThenResourceIsCreatedAndCanBeFound() {
|
||||
this.resources.addResource("test", "test-content", true);
|
||||
assertThat(this.root.resolve("test")).hasContent("test-content");
|
||||
assertThat(this.resources.find("test")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceHasContentReferencingResourceRootThenResourceIsCreatedWithReferenceToRoot() {
|
||||
new Resources(this.root).addResource("test", "*** ${resourceRoot} ***");
|
||||
this.resources.addResource("test", "*** ${resourceRoot} ***", true);
|
||||
assertThat(this.root.resolve("test")).hasContent("*** " + this.root + " ***");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceWithPathThenResourceIsCreated() {
|
||||
new Resources(this.root).addResource("a/b/c/test", "test-content");
|
||||
void whenAddResourceWithPathThenResourceIsCreatedAndItAndItsAncestorsCanBeFound() {
|
||||
this.resources.addResource("a/b/c/test", "test-content", true);
|
||||
assertThat(this.root.resolve("a/b/c/test")).hasContent("test-content");
|
||||
assertThat(this.resources.find("a/b/c/test")).isNotNull();
|
||||
assertThat(this.resources.find("a/b/c/")).isNotNull();
|
||||
assertThat(this.resources.find("a/b/")).isNotNull();
|
||||
assertThat(this.resources.find("a/")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceAndResourceAlreadyExistsThenResourcesIsOverwritten() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addResource("a/b/c/test", "original-content");
|
||||
resources.addResource("a/b/c/test", "new-content");
|
||||
this.resources.addResource("a/b/c/test", "original-content", true);
|
||||
this.resources.addResource("a/b/c/test", "new-content", true);
|
||||
assertThat(this.root.resolve("a/b/c/test")).hasContent("new-content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddPackageThenNamedResourcesFromPackageAreCreated() {
|
||||
new Resources(this.root).addPackage(getClass().getPackage(),
|
||||
new String[] { "resource-1.txt", "sub/resource-3.txt" });
|
||||
void whenAddPackageThenNamedResourcesFromPackageAreCreatedAndCanBeFound() {
|
||||
this.resources.addPackage(getClass().getPackage(), new String[] { "resource-1.txt", "sub/resource-3.txt" });
|
||||
assertThat(this.root.resolve("resource-1.txt")).hasContent("one");
|
||||
assertThat(this.root.resolve("resource-2.txt")).doesNotExist();
|
||||
assertThat(this.root.resolve("sub/resource-3.txt")).hasContent("three");
|
||||
assertThat(this.resources.find("resource-1.txt")).isNotNull();
|
||||
assertThat(this.resources.find("resource-2.txt")).isNull();
|
||||
assertThat(this.resources.find("sub/resource-3.txt")).isNotNull();
|
||||
assertThat(this.resources.find("sub/")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceAndDeleteThenResourceDoesNotExist() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addResource("test", "test-content");
|
||||
void whenAddResourceAndDeleteThenResourceDoesNotExistAndCannotBeFound() {
|
||||
this.resources.addResource("test", "test-content", true);
|
||||
assertThat(this.root.resolve("test")).hasContent("test-content");
|
||||
resources.delete();
|
||||
assertThat(this.resources.find("test")).isNotNull();
|
||||
this.resources.delete();
|
||||
assertThat(this.root.resolve("test")).doesNotExist();
|
||||
assertThat(this.resources.find("test")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddPackageAndDeleteThenResourcesDoNotExist() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addPackage(getClass().getPackage(),
|
||||
void whenAddPackageAndDeleteThenResourcesDoNotExistAndCannotBeFound() {
|
||||
this.resources.addPackage(getClass().getPackage(),
|
||||
new String[] { "resource-1.txt", "resource-2.txt", "sub/resource-3.txt" });
|
||||
assertThat(this.root.resolve("resource-1.txt")).hasContent("one");
|
||||
assertThat(this.root.resolve("resource-2.txt")).hasContent("two");
|
||||
assertThat(this.root.resolve("sub/resource-3.txt")).hasContent("three");
|
||||
resources.delete();
|
||||
assertThat(this.resources.find("resource-1.txt")).isNotNull();
|
||||
assertThat(this.resources.find("resource-2.txt")).isNotNull();
|
||||
assertThat(this.resources.find("sub/resource-3.txt")).isNotNull();
|
||||
assertThat(this.resources.find("sub/")).isNotNull();
|
||||
this.resources.delete();
|
||||
assertThat(this.root.resolve("resource-1.txt")).doesNotExist();
|
||||
assertThat(this.root.resolve("resource-2.txt")).doesNotExist();
|
||||
assertThat(this.root.resolve("sub/resource-3.txt")).doesNotExist();
|
||||
assertThat(this.root.resolve("sub")).doesNotExist();
|
||||
assertThat(this.resources.find("resource-1.txt")).isNull();
|
||||
assertThat(this.resources.find("resource-2.txt")).isNull();
|
||||
assertThat(this.resources.find("sub/resource-3.txt")).isNull();
|
||||
assertThat(this.resources.find("sub/")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddDirectoryThenDirectoryIsCreated() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addDirectory("dir");
|
||||
void whenAddDirectoryThenDirectoryIsCreatedAndCanBeFound() {
|
||||
this.resources.addDirectory("dir");
|
||||
assertThat(this.root.resolve("dir")).isDirectory();
|
||||
assertThat(this.resources.find("dir/")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddDirectoryWithPathThenDirectoryIsCreated() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addDirectory("one/two/three/dir");
|
||||
void whenAddDirectoryWithPathThenDirectoryIsCreatedAndItAndItsAncestorsCanBeFound() {
|
||||
this.resources.addDirectory("one/two/three/dir");
|
||||
assertThat(this.root.resolve("one/two/three/dir")).isDirectory();
|
||||
assertThat(this.resources.find("one/two/three/dir/")).isNotNull();
|
||||
assertThat(this.resources.find("one/two/three/")).isNotNull();
|
||||
assertThat(this.resources.find("one/two/")).isNotNull();
|
||||
assertThat(this.resources.find("one/")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddDirectoryAndDirectoryAlreadyExistsThenDoesNotThrow() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addDirectory("one/two/three/dir");
|
||||
resources.addDirectory("one/two/three/dir");
|
||||
this.resources.addDirectory("one/two/three/dir");
|
||||
this.resources.addDirectory("one/two/three/dir");
|
||||
assertThat(this.root.resolve("one/two/three/dir")).isDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddDirectoryAndResourceAlreadyExistsThenIllegalStateExceptionIsThrown() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addResource("one/two/three/", "content");
|
||||
assertThatIllegalStateException().isThrownBy(() -> resources.addDirectory("one/two/three"));
|
||||
this.resources.addResource("one/two/three/", "content", true);
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resources.addDirectory("one/two/three"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenAddResourceAndDirectoryAlreadyExistsThenIllegalStateExceptionIsThrown() {
|
||||
Resources resources = new Resources(this.root);
|
||||
resources.addDirectory("one/two/three");
|
||||
assertThatIllegalStateException().isThrownBy(() -> resources.addResource("one/two/three", "content"));
|
||||
this.resources.addDirectory("one/two/three");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resources.addResource("one/two/three", "content", true));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import java.nio.file.Path;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -97,4 +99,26 @@ class WithResourceTests {
|
|||
assertThat(new ClassPathResource("3").getContentAsString(StandardCharsets.UTF_8)).isEqualTo("three");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "org/springframework/boot/testsupport/classpath/resources/resource-1.txt",
|
||||
content = "from-with-resource")
|
||||
void whenWithResourceCreatesResourceThatIsAvailableElsewhereBothResourcesCanBeLoaded() throws IOException {
|
||||
Resource[] resources = new PathMatchingResourcePatternResolver()
|
||||
.getResources("classpath*:org/springframework/boot/testsupport/classpath/resources/resource-1.txt");
|
||||
assertThat(resources).hasSize(2);
|
||||
assertThat(resources).extracting((resource) -> resource.getContentAsString(StandardCharsets.UTF_8))
|
||||
.containsExactly("from-with-resource", "one");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "org/springframework/boot/testsupport/classpath/resources/resource-1.txt",
|
||||
content = "from-with-resource", additional = false)
|
||||
void whenWithResourceCreatesResourceThatIsNotAdditionalThenResourceThatIsAvailableElsewhereCannotBeLoaded()
|
||||
throws IOException {
|
||||
Resource[] resources = new PathMatchingResourcePatternResolver()
|
||||
.getResources("classpath*:org/springframework/boot/testsupport/classpath/resources/resource-1.txt");
|
||||
assertThat(resources).hasSize(1);
|
||||
assertThat(resources[0].getContentAsString(StandardCharsets.UTF_8)).isEqualTo("from-with-resource");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue