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(); }