From cffc870fd6e189a3f4fe30d9a44300703dff922a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 10 Jun 2019 09:24:06 +0100 Subject: [PATCH] Fix test failures on Windows Since the move to JUnit 5, a number of tests were failing on Windows. The majority were failing due to open file handles preventing the clean up of the tests' temporary directory. This commit addresses these failures by updating the tests to close JarFiles, InputStreams, OutputStreams etc. A change has also been made to CachingOperationInvokerTests to make a flakey test more robust. Due to System.currentTimeMillis() being less precise on Windows than it is on *nix platforms, the test could fail as it would not sleep for long enough for the TTL period to have expired. --- ...FileWebEndpointAutoConfigurationTests.java | 6 +- .../cache/CachingOperationInvokerTests.java | 5 +- .../logging/LogFileWebEndpointTests.java | 13 +- ...ooledDataSourceAutoConfigurationTests.java | 6 + .../classloader/RestartClassLoaderTests.java | 7 + ...figurationMetadataAnnotationProcessor.java | 5 +- .../loader/tools/MainClassFinderTests.java | 48 +++-- .../boot/loader/JarLauncherTests.java | 19 +- .../loader/LaunchedURLClassLoaderTests.java | 45 +++-- .../boot/loader/PropertiesLauncherTests.java | 4 +- .../boot/loader/WarLauncherTests.java | 19 +- .../loader/archive/ExplodedArchiveTests.java | 16 +- .../loader/archive/JarFileArchiveTests.java | 42 +++-- .../jar/CentralDirectoryParserTests.java | 9 +- .../boot/loader/jar/HandlerTests.java | 3 + .../boot/loader/jar/JarFileTests.java | 174 ++++++++++-------- .../loader/jar/JarURLConnectionTests.java | 97 ++++++---- .../loader/jar/JarUrlProtocolHandler.java | 57 ++++++ .../ApplicationPidFileWriterTests.java | 45 +++-- .../TomcatEmbeddedWebappClassLoaderTests.java | 10 +- .../undertow/JarResourceManagerTests.java | 9 +- ...mpleIntegrationParentApplicationTests.java | 5 +- 22 files changed, 435 insertions(+), 209 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java index 83d3f73e3d..2c5a22f44e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.logging; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -118,8 +119,9 @@ class LogFileWebEndpointAutoConfigurationTests { LogFileWebEndpoint endpoint = context.getBean(LogFileWebEndpoint.class); Resource resource = endpoint.logFile(); assertThat(resource).isNotNull(); - assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)) - .isEqualTo("--TEST--"); + try (InputStream input = resource.getInputStream()) { + assertThat(StreamUtils.copyToString(input, StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + } }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java index 322a771ecb..770effcda2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java @@ -114,7 +114,10 @@ class CachingOperationInvokerTests { given(target.invoke(context)).willReturn(new Object()); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L); invoker.invoke(context); - Thread.sleep(55); + long expired = System.currentTimeMillis() + 50; + while (System.currentTimeMillis() < expired) { + Thread.sleep(10); + } invoker.invoke(context); verify(target, times(2)).invoke(context); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LogFileWebEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LogFileWebEndpointTests.java index 2354def0d1..f9b7debe50 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LogFileWebEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/logging/LogFileWebEndpointTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.logging; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -70,7 +71,7 @@ class LogFileWebEndpointTests { this.environment.setProperty("logging.file.name", this.logFile.getAbsolutePath()); Resource resource = this.endpoint.logFile(); assertThat(resource).isNotNull(); - assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + assertThat(contentOf(resource)).isEqualTo("--TEST--"); } @Test @@ -79,7 +80,7 @@ class LogFileWebEndpointTests { this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); Resource resource = this.endpoint.logFile(); assertThat(resource).isNotNull(); - assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + assertThat(contentOf(resource)).isEqualTo("--TEST--"); } @Test @@ -87,7 +88,13 @@ class LogFileWebEndpointTests { LogFileWebEndpoint endpoint = new LogFileWebEndpoint(this.environment, this.logFile); Resource resource = endpoint.logFile(); assertThat(resource).isNotNull(); - assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + assertThat(contentOf(resource)).isEqualTo("--TEST--"); + } + + private String contentOf(Resource resource) throws IOException { + try (InputStream input = resource.getInputStream()) { + return StreamUtils.copyToString(input, StandardCharsets.UTF_8); + } } } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java index 7396e39707..9ebf3daf50 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java @@ -28,6 +28,8 @@ import org.apache.derby.jdbc.EmbeddedDriver; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @@ -121,6 +123,7 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat } @Test + @DisabledOnOs(OS.WINDOWS) void inMemoryDerbyIsShutdown() throws Exception { ConfigurableApplicationContext context = getContext( () -> createContext("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:test;create=true", @@ -132,6 +135,9 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat assertThatExceptionOfType(SQLException.class) .isThrownBy(() -> new EmbeddedDriver().connect("jdbc:derby:memory:test", new Properties())) .satisfies((ex) -> assertThat(ex.getSQLState()).isEqualTo("XJ004")); + // Shut Derby down fully so that it closes its log file + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> new EmbeddedDriver().connect("jdbc:derby:;shutdown=true", new Properties())); } } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java index 3cb76b87d4..f54914cd2c 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -72,6 +73,12 @@ class RestartClassLoaderTests { this.reloadClassLoader = new RestartClassLoader(this.parentClassLoader, urls, this.updatedFiles); } + @AfterEach + public void tearDown() throws Exception { + this.reloadClassLoader.close(); + this.parentClassLoader.close(); + } + private File createSampleJarFile(File tempDir) throws IOException { File file = new File(tempDir, "sample.jar"); JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java index ab3f3be62d..17ebf47617 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java @@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor.test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; @@ -96,7 +97,9 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM try { File metadataFile = new File(this.outputLocation, "META-INF/spring-configuration-metadata.json"); if (metadataFile.isFile()) { - this.metadata = new JsonMarshaller().read(new FileInputStream(metadataFile)); + try (InputStream input = new FileInputStream(metadataFile)) { + this.metadata = new JsonMarshaller().read(input); + } } else { this.metadata = new ConfigurationMetadata(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java index 68df9e437c..01013907ef 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.jar.JarFile; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,8 +53,10 @@ class MainClassFinderTests { void findMainClassInJar() throws Exception { this.testJarFile.addClass("B.class", ClassWithMainMethod.class); this.testJarFile.addClass("A.class", ClassWithoutMainMethod.class); - String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); - assertThat(actual).isEqualTo("B"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + String actual = MainClassFinder.findMainClass(jarFile, ""); + assertThat(actual).isEqualTo("B"); + } } @Test @@ -61,43 +64,52 @@ class MainClassFinderTests { this.testJarFile.addClass("a/b/c/D.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithoutMainMethod.class); this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class); - String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); - assertThat(actual).isEqualTo("a.b.c.D"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + String actual = MainClassFinder.findMainClass(jarFile, ""); + assertThat(actual).isEqualTo("a.b.c.D"); + } } @Test void usesBreadthFirstJarSearch() throws Exception { this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); - String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); - assertThat(actual).isEqualTo("a.B"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + String actual = MainClassFinder.findMainClass(jarFile, ""); + assertThat(actual).isEqualTo("a.B"); + } } @Test void findSingleJarSearch() throws Exception { this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); - assertThatIllegalStateException() - .isThrownBy(() -> MainClassFinder.findSingleMainClass(this.testJarFile.getJarFile(), "")) - .withMessageContaining( - "Unable to find a single main class " + "from the following candidates [a.B, a.b.c.E]"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + assertThatIllegalStateException().isThrownBy(() -> MainClassFinder.findSingleMainClass(jarFile, "")) + .withMessageContaining( + "Unable to find a single main class " + "from the following candidates [a.B, a.b.c.E]"); + } } @Test void findSingleJarSearchPrefersAnnotatedMainClass() throws Exception { this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", AnnotatedClassWithMainMethod.class); - String mainClass = MainClassFinder.findSingleMainClass(this.testJarFile.getJarFile(), "", - "org.springframework.boot.loader.tools.sample.SomeApplication"); - assertThat(mainClass).isEqualTo("a.b.c.E"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + String mainClass = MainClassFinder.findSingleMainClass(jarFile, "", + "org.springframework.boot.loader.tools.sample.SomeApplication"); + assertThat(mainClass).isEqualTo("a.b.c.E"); + } } @Test void findMainClassInJarSubLocation() throws Exception { this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); - String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), "a/"); - assertThat(actual).isEqualTo("B"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + String actual = MainClassFinder.findMainClass(jarFile, "a/"); + assertThat(actual).isEqualTo("B"); + } } @@ -163,8 +175,10 @@ class MainClassFinderTests { this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class); this.testJarFile.addClass("a/b/G.class", ClassWithMainMethod.class); ClassNameCollector callback = new ClassNameCollector(); - MainClassFinder.doWithMainClasses(this.testJarFile.getJarFile(), null, callback); - assertThat(callback.getClassNames().toString()).isEqualTo("[a.b.G, a.b.c.D]"); + try (JarFile jarFile = this.testJarFile.getJarFile()) { + MainClassFinder.doWithMainClasses(jarFile, null, callback); + assertThat(callback.getClassNames().toString()).isEqualTo("[a.b.G, a.b.c.D]"); + } } private static class ClassNameCollector implements MainClassCallback { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java index 554ea8f1e7..fa171150f5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java @@ -43,16 +43,25 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(), new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL()); + for (Archive archive : archives) { + archive.close(); + } } @Test void archivedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception { File jarRoot = createJarArchive("archive.jar", "BOOT-INF"); - JarLauncher launcher = new JarLauncher(new JarFileArchive(jarRoot)); - List archives = launcher.getClassPathArchives(); - assertThat(archives).hasSize(2); - assertThat(getUrls(archives)).containsOnly(new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/")); + try (JarFileArchive archive = new JarFileArchive(jarRoot)) { + JarLauncher launcher = new JarLauncher(archive); + List classPathArchives = launcher.getClassPathArchives(); + assertThat(classPathArchives).hasSize(2); + assertThat(getUrls(classPathArchives)).containsOnly( + new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"), + new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/")); + for (Archive classPathArchive : classPathArchives) { + classPathArchive.close(); + } + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java index c6cc64c313..c37e01de61 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java @@ -17,7 +17,10 @@ package org.springframework.boot.loader; import java.io.File; +import java.io.InputStream; +import java.net.JarURLConnection; import java.net.URL; +import java.net.URLConnection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -71,29 +74,37 @@ class LaunchedURLClassLoaderTests { void resolveFromNested() throws Exception { File file = new File(this.tempDir, "test.jar"); TestJarCreator.createTestJar(file); - JarFile jarFile = new JarFile(file); - URL url = jarFile.getUrl(); - LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null); - URL resource = loader.getResource("nested.jar!/3.dat"); - assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); - assertThat(resource.openConnection().getInputStream().read()).isEqualTo(3); + try (JarFile jarFile = new JarFile(file)) { + URL url = jarFile.getUrl(); + try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) { + URL resource = loader.getResource("nested.jar!/3.dat"); + assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); + try (InputStream input = resource.openConnection().getInputStream()) { + assertThat(input.read()).isEqualTo(3); + } + } + } } @Test void resolveFromNestedWhileThreadIsInterrupted() throws Exception { File file = new File(this.tempDir, "test.jar"); TestJarCreator.createTestJar(file); - JarFile jarFile = new JarFile(file); - URL url = jarFile.getUrl(); - LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null); - try { - Thread.currentThread().interrupt(); - URL resource = loader.getResource("nested.jar!/3.dat"); - assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); - assertThat(resource.openConnection().getInputStream().read()).isEqualTo(3); - } - finally { - Thread.interrupted(); + try (JarFile jarFile = new JarFile(file)) { + URL url = jarFile.getUrl(); + try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) { + Thread.currentThread().interrupt(); + URL resource = loader.getResource("nested.jar!/3.dat"); + assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); + URLConnection connection = resource.openConnection(); + try (InputStream input = connection.getInputStream()) { + assertThat(input.read()).isEqualTo(3); + } + ((JarURLConnection) connection).getJarFile().close(); + } + finally { + Thread.interrupted(); + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index bac4d2cfa9..6d05d9f641 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -312,7 +312,9 @@ class PropertiesLauncherTests { manifest.getMainAttributes().putValue("Loader-Path", "/foo.jar, /bar"); File manifestFile = new File(this.tempDir, "META-INF/MANIFEST.MF"); manifestFile.getParentFile().mkdirs(); - manifest.write(new FileOutputStream(manifestFile)); + try (FileOutputStream output = new FileOutputStream(manifestFile)) { + manifest.write(output); + } PropertiesLauncher launcher = new PropertiesLauncher(); assertThat((List) ReflectionTestUtils.getField(launcher, "paths")).containsExactly("/foo.jar", "/bar/"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java index c1b5259815..7b68a50d96 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java @@ -43,16 +43,25 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(), new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL()); + for (Archive archive : archives) { + archive.close(); + } } @Test void archivedWarHasOnlyWebInfClassesAndContentsOWebInfLibOnClasspath() throws Exception { File jarRoot = createJarArchive("archive.war", "WEB-INF"); - WarLauncher launcher = new WarLauncher(new JarFileArchive(jarRoot)); - List archives = launcher.getClassPathArchives(); - assertThat(archives).hasSize(2); - assertThat(getUrls(archives)).containsOnly(new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/")); + try (JarFileArchive archive = new JarFileArchive(jarRoot)) { + WarLauncher launcher = new WarLauncher(archive); + List classPathArchives = launcher.getClassPathArchives(); + assertThat(classPathArchives).hasSize(2); + assertThat(getUrls(classPathArchives)).containsOnly( + new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"), + new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/")); + for (Archive classPathArchive : classPathArchives) { + classPathArchive.close(); + } + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java index fd7cda2d1f..03b1f9d392 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java @@ -18,9 +18,6 @@ package org.springframework.boot.loader.archive; import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; @@ -36,6 +33,7 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.loader.TestJarCreator; import org.springframework.boot.loader.archive.Archive.Entry; +import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -81,22 +79,13 @@ class ExplodedArchiveTests { destination.mkdir(); } else { - copy(jarFile.getInputStream(entry), new FileOutputStream(destination)); + FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(destination)); } } this.archive = new ExplodedArchive(this.rootFolder); jarFile.close(); } - private void copy(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - } - @Test void getManifest() throws Exception { assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1"); @@ -124,6 +113,7 @@ class ExplodedArchiveTests { Entry entry = getEntriesMap(this.archive).get("nested.jar"); Archive nested = this.archive.getNestedArchive(entry); assertThat(nested.getUrl().toString()).isEqualTo(this.rootFolder.toURI() + "nested.jar"); + ((JarFileArchive) nested).close(); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java index 366bcac886..69fcb55889 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java @@ -28,6 +28,7 @@ import java.util.jar.JarOutputStream; import java.util.zip.CRC32; import java.util.zip.ZipEntry; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -61,10 +62,18 @@ class JarFileArchiveTests { setup(false); } + @AfterEach + public void tearDown() throws Exception { + this.archive.close(); + } + private void setup(boolean unpackNested) throws Exception { this.rootJarFile = new File(this.tempDir, "root.jar"); this.rootJarFileUrl = this.rootJarFile.toURI().toString(); TestJarCreator.createTestJar(this.rootJarFile, unpackNested); + if (this.archive != null) { + this.archive.close(); + } this.archive = new JarFileArchive(this.rootJarFile); } @@ -90,6 +99,7 @@ class JarFileArchiveTests { Entry entry = getEntriesMap(this.archive).get("nested.jar"); Archive nested = this.archive.getNestedArchive(entry); assertThat(nested.getUrl().toString()).isEqualTo("jar:" + this.rootJarFileUrl + "!/nested.jar!/"); + ((JarFileArchive) nested).close(); } @Test @@ -99,27 +109,36 @@ class JarFileArchiveTests { Archive nested = this.archive.getNestedArchive(entry); assertThat(nested.getUrl().toString()).startsWith("file:"); assertThat(nested.getUrl().toString()).endsWith("/nested.jar"); + ((JarFileArchive) nested).close(); } @Test void unpackedLocationsAreUniquePerArchive() throws Exception { setup(true); Entry entry = getEntriesMap(this.archive).get("nested.jar"); - URL firstNested = this.archive.getNestedArchive(entry).getUrl(); + Archive firstNested = this.archive.getNestedArchive(entry); + URL firstNestedUrl = firstNested.getUrl(); + ((JarFileArchive) firstNested).close(); + this.archive.close(); setup(true); entry = getEntriesMap(this.archive).get("nested.jar"); - URL secondNested = this.archive.getNestedArchive(entry).getUrl(); - assertThat(secondNested).isNotEqualTo(firstNested); + Archive secondNested = this.archive.getNestedArchive(entry); + URL secondNestedUrl = secondNested.getUrl(); + assertThat(secondNestedUrl).isNotEqualTo(firstNestedUrl); + ((JarFileArchive) secondNested).close(); } @Test void unpackedLocationsFromSameArchiveShareSameParent() throws Exception { setup(true); - File nested = new File( - this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar")).getUrl().toURI()); - File anotherNested = new File( - this.archive.getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar")).getUrl().toURI()); + Archive nestedArchive = this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar")); + File nested = new File(nestedArchive.getUrl().toURI()); + Archive anotherNestedArchive = this.archive + .getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar")); + File anotherNested = new File(anotherNestedArchive.getUrl().toURI()); assertThat(nested.getParent()).isEqualTo(anotherNested.getParent()); + ((JarFileArchive) nestedArchive).close(); + ((JarFileArchive) anotherNestedArchive).close(); } @Test @@ -147,10 +166,11 @@ class JarFileArchiveTests { output.closeEntry(); output.close(); JarFileArchive jarFileArchive = new JarFileArchive(file); - assertThatIllegalStateException() - .isThrownBy( - () -> jarFileArchive.getNestedArchive(getEntriesMap(jarFileArchive).get("nested/zip64.jar"))) - .withMessageContaining("Failed to get nested archive for entry nested/zip64.jar"); + assertThatIllegalStateException().isThrownBy(() -> { + Archive archive = jarFileArchive.getNestedArchive(getEntriesMap(jarFileArchive).get("nested/zip64.jar")); + ((JarFileArchive) archive).close(); + }).withMessageContaining("Failed to get nested archive for entry nested/zip64.jar"); + jarFileArchive.close(); } private byte[] writeZip64Jar() throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java index f9b7f4cd9b..6f1856b4d0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java @@ -17,10 +17,12 @@ package org.springframework.boot.loader.jar; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -40,7 +42,7 @@ class CentralDirectoryParserTests { private File jarFile; - private RandomAccessData jarData; + private RandomAccessDataFile jarData; @BeforeEach public void setup(@TempDir File tempDir) throws Exception { @@ -49,6 +51,11 @@ class CentralDirectoryParserTests { this.jarData = new RandomAccessDataFile(this.jarFile); } + @AfterEach + public void tearDown() throws IOException { + this.jarData.close(); + } + @Test void visitsInOrder() throws Exception { MockCentralDirectoryVisitor visitor = new MockCentralDirectoryVisitor(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java index 9b1e3fc0d7..3edb4627a9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java @@ -22,6 +22,7 @@ import java.net.URL; import java.net.URLConnection; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.loader.TestJarCreator; @@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ +@ExtendWith(JarUrlProtocolHandler.class) class HandlerTests { private final Handler handler = new Handler(); @@ -157,6 +159,7 @@ class HandlerTests { URLConnection connection = new URL(null, "jar:file:" + testJar.getAbsolutePath() + "!/nested.jar!/", this.handler).openConnection(); assertThat(connection).isInstanceOf(JarURLConnection.class); + ((JarURLConnection) connection).getJarFile().close(); URLConnection jdkConnection = new URL(null, "jar:file:file:" + testJar.getAbsolutePath() + "!/nested.jar!/", this.handler).openConnection(); assertThat(jdkConnection).isNotInstanceOf(JarURLConnection.class); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java index d0dec5c1f3..ec9a12943e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java @@ -32,8 +32,10 @@ import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.loader.TestJarCreator; @@ -54,6 +56,7 @@ import static org.mockito.Mockito.verify; * @author Martin Lau * @author Andy Wilkinson */ +@ExtendWith(JarUrlProtocolHandler.class) class JarFileTests { private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; @@ -74,6 +77,11 @@ class JarFileTests { this.jarFile = new JarFile(this.rootJarFile); } + @AfterEach + void tearDown() throws Exception { + this.jarFile.close(); + } + @Test void jdkJarFile() throws Exception { // Sanity checks to see how the default jar file operates @@ -96,8 +104,8 @@ class JarFileTests { URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarUrl }); assertThat(urlClassLoader.getResource("special/\u00EB.dat")).isNotNull(); assertThat(urlClassLoader.getResource("d/9.dat")).isNotNull(); - jarFile.close(); urlClassLoader.close(); + jarFile.close(); } @Test @@ -243,77 +251,82 @@ class JarFileTests { @Test void getNestedJarFile() throws Exception { - JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); + try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + Enumeration entries = nestedJarFile.entries(); + assertThat(entries.nextElement().getName()).isEqualTo("META-INF/"); + assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF"); + assertThat(entries.nextElement().getName()).isEqualTo("3.dat"); + assertThat(entries.nextElement().getName()).isEqualTo("4.dat"); + assertThat(entries.nextElement().getName()).isEqualTo("\u00E4.dat"); + assertThat(entries.hasMoreElements()).isFalse(); - Enumeration entries = nestedJarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/"); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF"); - assertThat(entries.nextElement().getName()).isEqualTo("3.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("4.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("\u00E4.dat"); - assertThat(entries.hasMoreElements()).isFalse(); + InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("3.dat")); + assertThat(inputStream.read()).isEqualTo(3); + assertThat(inputStream.read()).isEqualTo(-1); - InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("3.dat")); - assertThat(inputStream.read()).isEqualTo(3); - assertThat(inputStream.read()).isEqualTo(-1); - - URL url = nestedJarFile.getUrl(); - assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/"); - JarURLConnection conn = (JarURLConnection) url.openConnection(); - assertThat(conn.getJarFile()).isSameAs(nestedJarFile); - assertThat(conn.getJarFileURL().toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); - assertThat(conn.getInputStream()).isNotNull(); - JarInputStream jarInputStream = new JarInputStream(conn.getInputStream()); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat"); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat"); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat"); - jarInputStream.close(); - assertThat(conn.getPermission()).isInstanceOf(FilePermission.class); - FilePermission permission = (FilePermission) conn.getPermission(); - assertThat(permission.getActions()).isEqualTo("read"); - assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath()); + URL url = nestedJarFile.getUrl(); + assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/"); + JarURLConnection conn = (JarURLConnection) url.openConnection(); + assertThat(conn.getJarFile()).isSameAs(nestedJarFile); + assertThat(conn.getJarFileURL().toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); + assertThat(conn.getInputStream()).isNotNull(); + JarInputStream jarInputStream = new JarInputStream(conn.getInputStream()); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat"); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat"); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat"); + jarInputStream.close(); + assertThat(conn.getPermission()).isInstanceOf(FilePermission.class); + FilePermission permission = (FilePermission) conn.getPermission(); + assertThat(permission.getActions()).isEqualTo("read"); + assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath()); + } } @Test void getNestedJarDirectory() throws Exception { - JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("d/")); + try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("d/"))) { + Enumeration entries = nestedJarFile.entries(); + assertThat(entries.nextElement().getName()).isEqualTo("9.dat"); + assertThat(entries.hasMoreElements()).isFalse(); - Enumeration entries = nestedJarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("9.dat"); - assertThat(entries.hasMoreElements()).isFalse(); + try (InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("9.dat"))) { + assertThat(inputStream.read()).isEqualTo(9); + assertThat(inputStream.read()).isEqualTo(-1); + } - InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("9.dat")); - assertThat(inputStream.read()).isEqualTo(9); - assertThat(inputStream.read()).isEqualTo(-1); - - URL url = nestedJarFile.getUrl(); - assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/d!/"); - assertThat(((JarURLConnection) url.openConnection()).getJarFile()).isSameAs(nestedJarFile); + URL url = nestedJarFile.getUrl(); + assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/d!/"); + assertThat(((JarURLConnection) url.openConnection()).getJarFile()).isSameAs(nestedJarFile); + } } @Test void getNestedJarEntryUrl() throws Exception { - JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - URL url = nestedJarFile.getJarEntry("3.dat").getUrl(); - assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat"); - InputStream inputStream = url.openStream(); - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(3); + try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + URL url = nestedJarFile.getJarEntry("3.dat").getUrl(); + assertThat(url.toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat"); + try (InputStream inputStream = url.openStream()) { + assertThat(inputStream).isNotNull(); + assertThat(inputStream.read()).isEqualTo(3); + } + } } @Test void createUrlFromString() throws Exception { - JarFile.registerUrlProtocolHandler(); String spec = "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat"; URL url = new URL(spec); assertThat(url.toString()).isEqualTo(spec); - InputStream inputStream = url.openStream(); - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(3); JarURLConnection connection = (JarURLConnection) url.openConnection(); - assertThat(connection.getURL().toString()).isEqualTo(spec); - assertThat(connection.getJarFileURL().toString()).isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); - assertThat(connection.getEntryName()).isEqualTo("3.dat"); + try (InputStream inputStream = connection.getInputStream()) { + assertThat(inputStream).isNotNull(); + assertThat(inputStream.read()).isEqualTo(3); + assertThat(connection.getURL().toString()).isEqualTo(spec); + assertThat(connection.getJarFileURL().toString()) + .isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); + assertThat(connection.getEntryName()).isEqualTo("3.dat"); + connection.getJarFile().close(); + } } @Test @@ -330,13 +343,15 @@ class JarFileTests { JarFile.registerUrlProtocolHandler(); URL url = new URL(spec); assertThat(url.toString()).isEqualTo(spec); - InputStream inputStream = url.openStream(); - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(2); JarURLConnection connection = (JarURLConnection) url.openConnection(); - assertThat(connection.getURL().toString()).isEqualTo(spec); - assertThat(connection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI()); - assertThat(connection.getEntryName()).isEqualTo("2.dat"); + try (InputStream inputStream = connection.getInputStream()) { + assertThat(inputStream).isNotNull(); + assertThat(inputStream.read()).isEqualTo(2); + assertThat(connection.getURL().toString()).isEqualTo(spec); + assertThat(connection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI()); + assertThat(connection.getEntryName()).isEqualTo("2.dat"); + } + connection.getJarFile().close(); } @Test @@ -356,8 +371,9 @@ class JarFileTests { @Test void sensibleToString() throws Exception { assertThat(this.jarFile.toString()).isEqualTo(this.rootJarFile.getPath()); - assertThat(this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")).toString()) - .isEqualTo(this.rootJarFile.getPath() + "!/nested.jar"); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + assertThat(nested.toString()).isEqualTo(this.rootJarFile.getPath() + "!/nested.jar"); + } } @Test @@ -395,6 +411,7 @@ class JarFileTests { StreamUtils.copy("#/bin/bash", Charset.defaultCharset(), outputStream); FileCopyUtils.copy(sourceJarContent, outputStream); this.rootJarFile = file; + this.jarFile.close(); this.jarFile = new JarFile(file); // Call some other tests to verify getEntries(); @@ -404,10 +421,11 @@ class JarFileTests { @Test void cannotLoadMissingJar() throws Exception { // relates to gh-1070 - JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - URL nestedUrl = nestedJarFile.getUrl(); - URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat"); - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(url.openConnection()::getInputStream); + try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + URL nestedUrl = nestedJarFile.getUrl(); + URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat"); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(url.openConnection()::getInputStream); + } } @Test @@ -462,13 +480,14 @@ class JarFileTests { // gh-12483 JarURLConnection.setUseFastExceptions(true); try { - JarFile.registerUrlProtocolHandler(); - JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - URL context = nested.getUrl(); - new URL(context, "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat").openConnection() - .getInputStream().close(); - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( - new URL(context, "jar:" + this.rootJarFile.toURI() + "!/no.dat").openConnection()::getInputStream); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + URL context = nested.getUrl(); + new URL(context, "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat").openConnection() + .getInputStream().close(); + assertThatExceptionOfType(FileNotFoundException.class) + .isThrownBy(new URL(context, "jar:" + this.rootJarFile.toURI() + "!/no.dat") + .openConnection()::getInputStream); + } } finally { JarURLConnection.setUseFastExceptions(false); @@ -477,12 +496,13 @@ class JarFileTests { @Test void multiReleaseEntry() throws Exception { - JarFile multiRelease = this.jarFile.getNestedJarFile(this.jarFile.getEntry("multi-release.jar")); - ZipEntry entry = multiRelease.getEntry("multi-release.dat"); - assertThat(entry.getName()).isEqualTo("multi-release.dat"); - InputStream inputStream = multiRelease.getInputStream(entry); - assertThat(inputStream.available()).isEqualTo(1); - assertThat(inputStream.read()).isEqualTo(getJavaVersion()); + try (JarFile multiRelease = this.jarFile.getNestedJarFile(this.jarFile.getEntry("multi-release.jar"))) { + ZipEntry entry = multiRelease.getEntry("multi-release.dat"); + assertThat(entry.getName()).isEqualTo("multi-release.dat"); + InputStream inputStream = multiRelease.getInputStream(entry); + assertThat(inputStream.available()).isEqualTo(1); + assertThat(inputStream.read()).isEqualTo(getJavaVersion()); + } } private int getJavaVersion() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java index ca578cda87..be2b51fc09 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java @@ -19,8 +19,10 @@ package org.springframework.boot.loader.jar; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; +import java.io.InputStream; import java.net.URL; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -51,6 +53,11 @@ class JarURLConnectionTests { this.jarFile = new JarFile(this.rootJarFile); } + @AfterEach + public void tearDown() throws Exception { + this.jarFile.close(); + } + @Test void connectionToRootUsingAbsoluteUrl() throws Exception { URL url = new URL("jar:file:" + getAbsolutePath() + "!/"); @@ -66,97 +73,123 @@ class JarURLConnectionTests { @Test void connectionToEntryUsingAbsoluteUrl() throws Exception { URL url = new URL("jar:file:" + getAbsolutePath() + "!/1.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + } } @Test void connectionToEntryUsingRelativeUrl() throws Exception { URL url = new URL("jar:file:" + getRelativePath() + "!/1.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + } } @Test void connectionToEntryUsingAbsoluteUrlWithFileColonSlashSlashPrefix() throws Exception { URL url = new URL("jar:file:/" + getAbsolutePath() + "!/1.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); + } } @Test void connectionToEntryUsingAbsoluteUrlForNestedEntry() throws Exception { URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + JarURLConnection connection = JarURLConnection.get(url, this.jarFile); + try (InputStream input = connection.getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + connection.getJarFile().close(); } @Test void connectionToEntryUsingRelativeUrlForNestedEntry() throws Exception { URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + JarURLConnection connection = JarURLConnection.get(url, this.jarFile); + try (InputStream input = connection.getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + connection.getJarFile().close(); } @Test void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile() throws Exception { URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); - JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - assertThat(JarURLConnection.get(url, nested).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + } } @Test void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile() throws Exception { URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); - JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - assertThat(JarURLConnection.get(url, nested).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + } } @Test void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() throws Exception { URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), "/3.dat"); - JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - assertThat(JarURLConnection.get(url, nested).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + } } @Test void connectionToEntryWithSpaceNestedEntry() throws Exception { URL url = new URL("jar:file:" + getRelativePath() + "!/space nested.jar!/3.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + JarURLConnection connection = JarURLConnection.get(url, this.jarFile); + try (InputStream input = connection.getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + connection.getJarFile().close(); } @Test void connectionToEntryWithEncodedSpaceNestedEntry() throws Exception { URL url = new URL("jar:file:" + getRelativePath() + "!/space%20nested.jar!/3.dat"); - assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) - .hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + JarURLConnection connection = JarURLConnection.get(url, this.jarFile); + try (InputStream input = connection.getInputStream()) { + assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); + } + connection.getJarFile().close(); } @Test void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() throws Exception { URL url = new URL("jar:file:" + getAbsolutePath() + "!/w.jar!/3.dat"); - JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); - assertThatExceptionOfType(FileNotFoundException.class) - .isThrownBy(JarURLConnection.get(url, nested)::getInputStream); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + assertThatExceptionOfType(FileNotFoundException.class) + .isThrownBy(JarURLConnection.get(url, nested)::getInputStream); + } } @Test void getContentLengthReturnsLengthOfUnderlyingEntry() throws Exception { - URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), - "/3.dat"); - assertThat(url.openConnection().getContentLength()).isEqualTo(1); + URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + JarURLConnection connection = JarURLConnection.get(url, nested); + assertThat(connection.getContentLength()).isEqualTo(1); + } } @Test void getContentLengthLongReturnsLengthOfUnderlyingEntry() throws Exception { - URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), - "/3.dat"); - assertThat(url.openConnection().getContentLengthLong()).isEqualTo(1); + URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); + try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { + JarURLConnection connection = JarURLConnection.get(url, nested); + assertThat(connection.getContentLengthLong()).isEqualTo(1); + } } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java new file mode 100644 index 0000000000..93aaade566 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2018 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.loader.jar; + +import java.io.File; +import java.lang.ref.SoftReference; +import java.util.Map; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.springframework.test.util.ReflectionTestUtils; + +/** + * JUnit 5 {@link Extension} for tests that interact with Spring Boot's {@link Handler} + * for {@code jar:} URLs. Ensures that the handler is registered prior to test execution + * and cleans up the handler's root file cache afterwards. + * + * @author Andy Wilkinson + */ +class JarUrlProtocolHandler implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + JarFile.registerUrlProtocolHandler(); + } + + @Override + @SuppressWarnings("unchecked") + public void afterEach(ExtensionContext context) throws Exception { + Map rootFileCache = ((SoftReference>) ReflectionTestUtils + .getField(Handler.class, "rootFileCache")).get(); + if (rootFileCache != null) { + for (JarFile rootJarFile : rootFileCache.values()) { + rootJarFile.close(); + } + rootFileCache.clear(); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java index 5764afbe31..78f6643de5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java @@ -17,6 +17,8 @@ package org.springframework.boot.context; import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -127,34 +129,43 @@ class ApplicationPidFileWriterTests { @Test void continueWhenPidFileIsReadOnly() throws Exception { - File file = new File(this.tempDir, "pid"); - file.createNewFile(); - file.setReadOnly(); - ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); - listener.onApplicationEvent(EVENT); - assertThat(contentOf(file)).isEmpty(); + withReadOnlyPidFile((file) -> { + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + listener.onApplicationEvent(EVENT); + assertThat(contentOf(file)).isEmpty(); + }); } @Test void throwWhenPidFileIsReadOnly() throws Exception { - File file = new File(this.tempDir, "pid"); - file.createNewFile(); - file.setReadOnly(); - System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true"); - ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); - assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(EVENT)) - .withMessageContaining("Cannot create pid file"); + withReadOnlyPidFile((file) -> { + System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true"); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(EVENT)) + .withMessageContaining("Cannot create pid file"); + }); } @Test void throwWhenPidFileIsReadOnlyWithSpring() throws Exception { + withReadOnlyPidFile((file) -> { + SpringApplicationEvent event = createPreparedEvent("spring.pid.fail-on-write-error", "true"); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(event)) + .withMessageContaining("Cannot create pid file"); + }); + } + + private void withReadOnlyPidFile(Consumer consumer) throws IOException { File file = new File(this.tempDir, "pid"); file.createNewFile(); file.setReadOnly(); - SpringApplicationEvent event = createPreparedEvent("spring.pid.fail-on-write-error", "true"); - ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); - assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(event)) - .withMessageContaining("Cannot create pid file"); + try { + consumer.accept(file); + } + finally { + file.setWritable(true); + } } private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedWebappClassLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedWebappClassLoaderTests.java index 86b8b10620..dc8b180c8c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedWebappClassLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedWebappClassLoaderTests.java @@ -75,8 +75,16 @@ class TomcatEmbeddedWebappClassLoaderTests { resources.start(); classLoader.setResources(resources); classLoader.start(); - consumer.accept(classLoader); + try { + consumer.accept(classLoader); + } + finally { + classLoader.stop(); + classLoader.close(); + resources.stop(); + } } + parent.close(); } private String webInfClassesUrlString(File war) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/JarResourceManagerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/JarResourceManagerTests.java index 0d91fd07c8..ecdae926b6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/JarResourceManagerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/JarResourceManagerTests.java @@ -42,10 +42,11 @@ class JarResourceManagerTests { @BeforeEach public void createJar(@TempDir File tempDir) throws IOException { File jar = new File(tempDir, "test.jar"); - JarOutputStream out = new JarOutputStream(new FileOutputStream(jar)); - out.putNextEntry(new ZipEntry("hello.txt")); - out.write("hello".getBytes()); - out.close(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { + out.putNextEntry(new ZipEntry("hello.txt")); + out.write("hello".getBytes()); + out.close(); + } this.resourceManager = new JarResourceManager(jar); } diff --git a/spring-boot-samples/spring-boot-sample-parent-context/src/test/java/sample/parent/consumer/SampleIntegrationParentApplicationTests.java b/spring-boot-samples/spring-boot-sample-parent-context/src/test/java/sample/parent/consumer/SampleIntegrationParentApplicationTests.java index 0812cd1bfe..ab80bf749d 100644 --- a/spring-boot-samples/spring-boot-sample-parent-context/src/test/java/sample/parent/consumer/SampleIntegrationParentApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-parent-context/src/test/java/sample/parent/consumer/SampleIntegrationParentApplicationTests.java @@ -18,6 +18,7 @@ package sample.parent.consumer; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import org.junit.jupiter.api.Test; @@ -94,7 +95,9 @@ class SampleIntegrationParentApplicationTests { private String readResources(Resource[] resources) throws IOException { StringBuilder builder = new StringBuilder(); for (Resource resource : resources) { - builder.append(new String(StreamUtils.copyToByteArray(resource.getInputStream()))); + try (InputStream input = resource.getInputStream()) { + builder.append(new String(StreamUtils.copyToByteArray(input))); + } } return builder.toString(); }