提交 f7bc8c82 编写于 作者: A Andy Wilkinson 提交者: Sam Brannen

Tidy up classpath pollution caused by resource creation in the tests

Previously, spring-webmvc and spring-webflux both contained tests
that would create gzipped files, write them to the filesystem
alongside the project's compiled test classes, and configure them to
be deleted on JVM exit. The output location placed the files on the
classpath, polluting it for every subsequent test that used the same
ClassLoader. The test-sources plugin combined with Gradle's use of
worker JVMs, broadens the scope of this pollution to other, downstream
projects in the same build. For example, the tests for
spring-websocket will have a different classpath depending on whether
or not the tests for spring-webmvc have already been run on the same
worker as part of the current build.

This commit updates the spring-webmvc and spring-webflux modules to
introduce a new JUnit Jupiter extension, GzipSupport. This extension
allows gzipped files to be created via an injectable GzippedFiles
class and automatically deletes each created file in an after-each
callback. This ensures that a gzipped file only exists on the
classpath for the duration of the test that needs it, avoiding the
pollution of the classpath of any subsequent tests.

Closes gh-23970
上级 b5fb2828
......@@ -23,6 +23,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.cache.Cache;
......@@ -30,6 +31,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.web.test.server.MockServerWebExchange;
import org.springframework.web.reactive.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get;
......@@ -39,6 +41,7 @@ import static org.springframework.mock.http.server.reactive.test.MockServerHttpR
*
* @author Rossen Stoyanchev
*/
@ExtendWith(GzipSupport.class)
public class CachingResourceResolverTests {
private static final Duration TIMEOUT = Duration.ofSeconds(5);
......@@ -116,10 +119,10 @@ public class CachingResourceResolverTests {
}
@Test
public void resolveResourceAcceptEncodingInCacheKey() throws IOException {
public void resolveResourceAcceptEncodingInCacheKey(GzippedFiles gzippedFiles) throws IOException {
String file = "bar.css";
EncodedResourceResolverTests.createGzippedFile(file);
gzippedFiles.create(file);
// 1. Resolve plain resource
......
......@@ -23,6 +23,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import reactor.test.StepVerifier;
......@@ -31,6 +32,7 @@ import org.springframework.core.io.Resource;
import org.springframework.mock.web.test.server.MockServerWebExchange;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.resource.EncodedResourceResolver.EncodedResource;
import org.springframework.web.reactive.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get;
......@@ -41,6 +43,7 @@ import static org.springframework.mock.http.server.reactive.test.MockServerHttpR
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
@ExtendWith(GzipSupport.class)
public class CssLinkResourceTransformerTests {
private ResourceTransformerChain transformerChain;
......@@ -147,9 +150,8 @@ public class CssLinkResourceTransformerTests {
}
@Test
public void transformSkippedForGzippedResource() throws Exception {
EncodedResourceResolverTests.createGzippedFile("main.css");
public void transformSkippedForGzippedResource(GzippedFiles gzippedFiles) throws Exception {
gzippedFiles.create("main.css");
MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/main.css"));
Resource resource = getResource("main.css");
......
......@@ -16,31 +16,23 @@
package org.springframework.web.reactive.resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.web.test.server.MockServerWebExchange;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.reactive.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -49,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rossen Stoyanchev
*/
@ExtendWith(GzipSupport.class)
public class EncodedResourceResolverTests {
private static final Duration TIMEOUT = Duration.ofSeconds(5);
......@@ -59,26 +52,6 @@ public class EncodedResourceResolverTests {
private List<Resource> locations;
@BeforeAll
public static void createGzippedResources() throws IOException {
createGzippedFile("/js/foo.js");
createGzippedFile("foo.css");
}
static void createGzippedFile(String filePath) throws IOException {
Resource location = new ClassPathResource("test/", EncodedResourceResolverTests.class);
Resource resource = new FileSystemResource(location.createRelative(filePath).getFile());
Path gzFilePath = Paths.get(resource.getFile().getAbsolutePath() + ".gz");
Files.deleteIfExists(gzFilePath);
File gzFile = Files.createFile(gzFilePath).toFile();
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFile));
FileCopyUtils.copy(resource.getInputStream(), out);
gzFile.deleteOnExit();
}
@BeforeEach
public void setup() {
Cache cache = new ConcurrentMapCache("resourceCache");
......@@ -100,12 +73,13 @@ public class EncodedResourceResolverTests {
@Test
public void resolveGzipped() {
public void resolveGzipped(GzippedFiles gzippedFiles) {
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("").header("Accept-Encoding", "gzip"));
String file = "js/foo.js";
gzippedFiles.create(file);
Resource actual = this.resolver.resolveResource(exchange, file, this.locations).block(TIMEOUT);
assertThat(actual.getDescription()).isEqualTo(getResource(file + ".gz").getDescription());
......@@ -119,11 +93,11 @@ public class EncodedResourceResolverTests {
}
@Test
public void resolveGzippedWithVersion() {
public void resolveGzippedWithVersion(GzippedFiles gzippedFiles) {
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("").header("Accept-Encoding", "gzip"));
gzippedFiles.create("foo.css");
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
Resource actual = this.resolver.resolveResource(exchange, file, this.locations).block(TIMEOUT);
......@@ -134,7 +108,7 @@ public class EncodedResourceResolverTests {
}
@Test
public void resolveFromCacheWithEncodingVariants() {
public void resolveFromCacheWithEncodingVariants(GzippedFiles gzippedFiles) {
// 1. Resolve, and cache .gz variant
......@@ -142,6 +116,7 @@ public class EncodedResourceResolverTests {
MockServerHttpRequest.get("").header("Accept-Encoding", "gzip"));
String file = "js/foo.js";
gzippedFiles.create(file);
Resource resolved = this.resolver.resolveResource(exchange, file, this.locations).block(TIMEOUT);
assertThat(resolved.getDescription()).isEqualTo(getResource(file + ".gz").getDescription());
......
/*
* Copyright 2002-2019 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.web.reactive.resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
class GzipSupport implements AfterEachCallback, ParameterResolver {
@Override
public void afterEach(ExtensionContext context) throws Exception {
GzippedFiles gzippedFiles = getStore(context).get(GzippedFiles.class, GzippedFiles.class);
if (gzippedFiles != null) {
for (File gzippedFile: gzippedFiles.created) {
gzippedFile.delete();
}
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType().equals(GzippedFiles.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return getStore(extensionContext).getOrComputeIfAbsent(GzippedFiles.class);
}
private Store getStore(ExtensionContext extensionContext) {
return extensionContext.getStore(Namespace.create(getClass()));
}
static class GzippedFiles {
private final Set<File> created = new HashSet<>();
void create(String filePath) {
try {
Resource location = new ClassPathResource("test/", EncodedResourceResolverTests.class);
Resource resource = new FileSystemResource(location.createRelative(filePath).getFile());
Path gzFilePath = Paths.get(resource.getFile().getAbsolutePath() + ".gz");
Files.deleteIfExists(gzFilePath);
File gzFile = Files.createFile(gzFilePath).toFile();
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFile));
FileCopyUtils.copy(resource.getInputStream(), out);
created.add(gzFile);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
......@@ -22,6 +22,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.cache.Cache;
......@@ -29,6 +30,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.web.servlet.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -38,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rossen Stoyanchev
*/
@ExtendWith(GzipSupport.class)
public class CachingResourceResolverTests {
private Cache cache;
......@@ -108,10 +111,10 @@ public class CachingResourceResolverTests {
}
@Test
public void resolveResourceAcceptEncodingInCacheKey() throws IOException {
public void resolveResourceAcceptEncodingInCacheKey(GzippedFiles gzippedFiles) throws IOException {
String file = "bar.css";
EncodedResourceResolverTests.createGzippedFile(file);
gzippedFiles.create(file);
// 1. Resolve plain resource
......
......@@ -23,6 +23,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.core.io.ClassPathResource;
......@@ -30,6 +31,7 @@ import org.springframework.core.io.Resource;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.resource.EncodedResourceResolver.EncodedResource;
import org.springframework.web.servlet.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -41,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Sam Brannen
* @since 4.1
*/
@ExtendWith(GzipSupport.class)
public class CssLinkResourceTransformerTests {
private ResourceTransformerChain transformerChain;
......@@ -137,9 +140,8 @@ public class CssLinkResourceTransformerTests {
}
@Test
public void transformSkippedForGzippedResource() throws Exception {
EncodedResourceResolverTests.createGzippedFile("main.css");
public void transformSkippedForGzippedResource(GzippedFiles gzippedFiles) throws Exception {
gzippedFiles.create("main.css");
this.request = new MockHttpServletRequest("GET", "/static/main.css");
Resource original = new ClassPathResource("test/main.css", getClass());
......
......@@ -16,29 +16,21 @@
package org.springframework.web.servlet.resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.resource.GzipSupport.GzippedFiles;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -48,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Jeremy Grelle
* @author Rossen Stoyanchev
*/
@ExtendWith(GzipSupport.class)
public class EncodedResourceResolverTests {
private ResourceResolverChain resolver;
......@@ -56,27 +49,6 @@ public class EncodedResourceResolverTests {
private Cache cache;
@BeforeAll
public static void createGzippedResources() throws IOException {
createGzippedFile("/js/foo.js");
createGzippedFile("foo.css");
}
static void createGzippedFile(String filePath) throws IOException {
Resource location = new ClassPathResource("test/", EncodedResourceResolverTests.class);
Resource resource = new FileSystemResource(location.createRelative(filePath).getFile());
Path gzFilePath = Paths.get(resource.getFile().getAbsolutePath() + ".gz");
Files.deleteIfExists(gzFilePath);
File gzFile = Files.createFile(gzFilePath).toFile();
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFile));
FileCopyUtils.copy(resource.getInputStream(), out);
gzFile.deleteOnExit();
}
@BeforeEach
public void setup() {
this.cache = new ConcurrentMapCache("resourceCache");
......@@ -98,8 +70,9 @@ public class EncodedResourceResolverTests {
@Test
public void resolveGzipped() {
public void resolveGzipped(GzippedFiles gzippedFiles) {
String file = "js/foo.js";
gzippedFiles.create(file);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept-Encoding", "gzip");
Resource actual = this.resolver.resolveResource(request, file, this.locations);
......@@ -115,7 +88,8 @@ public class EncodedResourceResolverTests {
}
@Test
public void resolveGzippedWithVersion() {
public void resolveGzippedWithVersion(GzippedFiles gzippedFiles) {
gzippedFiles.create("foo.css");
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept-Encoding", "gzip");
......@@ -128,9 +102,10 @@ public class EncodedResourceResolverTests {
}
@Test
public void resolveFromCacheWithEncodingVariants() {
public void resolveFromCacheWithEncodingVariants(GzippedFiles gzippedFiles) {
// 1. Resolve, and cache .gz variant
String file = "js/foo.js";
gzippedFiles.create(file);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/js/foo.js");
request.addHeader("Accept-Encoding", "gzip");
Resource resolved = this.resolver.resolveResource(request, file, this.locations);
......
/*
* Copyright 2002-2019 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.web.servlet.resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
class GzipSupport implements AfterEachCallback, ParameterResolver {
@Override
public void afterEach(ExtensionContext context) throws Exception {
GzippedFiles gzippedFiles = getStore(context).get(GzippedFiles.class, GzippedFiles.class);
if (gzippedFiles != null) {
for (File gzippedFile: gzippedFiles.created) {
gzippedFile.delete();
}
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType().equals(GzippedFiles.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return getStore(extensionContext).getOrComputeIfAbsent(GzippedFiles.class);
}
private Store getStore(ExtensionContext extensionContext) {
return extensionContext.getStore(Namespace.create(getClass()));
}
static class GzippedFiles {
private final Set<File> created = new HashSet<>();
void create(String filePath) {
try {
Resource location = new ClassPathResource("test/", EncodedResourceResolverTests.class);
Resource resource = new FileSystemResource(location.createRelative(filePath).getFile());
Path gzFilePath = Paths.get(resource.getFile().getAbsolutePath() + ".gz");
Files.deleteIfExists(gzFilePath);
File gzFile = Files.createFile(gzFilePath).toFile();
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFile));
FileCopyUtils.copy(resource.getInputStream(), out);
created.add(gzFile);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
......@@ -73,7 +73,7 @@
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/].+TestNGTests" checks="IllegalImport" id="bannedTestNGImports" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]context[\\/]junit[\\/]jupiter[\\/]web[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]util[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/](client|reactive|servlet)[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/](client|reactive|servlet)[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]context[\\/]junit4" checks="SpringJUnit5" />
<suppress files="ContextHierarchyDirtiesContextTests|ClassLevelDirtiesContextTests|ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests|ContextConfigurationWithPropertiesExtendingPropertiesTests|DirtiesContextInterfaceTests|.+WacTests|JUnit4SpringContextWebTests" checks="SpringJUnit5" />
<suppress files=".+TestSuite|ContextHierarchyDirtiesContextTests|ClassLevelDirtiesContextTests|ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests|ContextConfigurationWithPropertiesExtendingPropertiesTests|DirtiesContextInterfaceTests|.+WacTests|JUnit4SpringContextWebTests" checks="IllegalImport" id="bannedJUnit4Imports" />
......@@ -89,11 +89,15 @@
<suppress files="PatternParseException" checks="JavadocVariable" />
<suppress files="web[\\/]reactive[\\/]socket[\\/]CloseStatus" checks="JavadocStyle" />
<!-- spring-webflux -->
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]reactive[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports" />
<!-- spring-webmvc -->
<suppress files="org[\\/]springframework[\\/]web[\\/]servlet[\\/]tags[\\/]form[\\/].*Tag" checks="JavadocVariable" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]protobuf[\\/].*" checks=".*" />
<suppress files="ExtractingResponseErrorHandlerTests" checks="MutableException" />
<suppress files="ServletAnnotationControllerHandlerMethodTests" checks="InterfaceIsType" />
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]servlet[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports" />
<!-- spring-websocket -->
<suppress files="web[\\/]socket[\\/]CloseStatus" checks="JavadocStyle" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册