Commit cffc870f authored by Andy Wilkinson's avatar Andy Wilkinson

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.
parent c56fbf8c
...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.logging; ...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.logging;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
...@@ -118,8 +119,9 @@ class LogFileWebEndpointAutoConfigurationTests { ...@@ -118,8 +119,9 @@ class LogFileWebEndpointAutoConfigurationTests {
LogFileWebEndpoint endpoint = context.getBean(LogFileWebEndpoint.class); LogFileWebEndpoint endpoint = context.getBean(LogFileWebEndpoint.class);
Resource resource = endpoint.logFile(); Resource resource = endpoint.logFile();
assertThat(resource).isNotNull(); assertThat(resource).isNotNull();
assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)) try (InputStream input = resource.getInputStream()) {
.isEqualTo("--TEST--"); assertThat(StreamUtils.copyToString(input, StandardCharsets.UTF_8)).isEqualTo("--TEST--");
}
}); });
} }
......
...@@ -114,7 +114,10 @@ class CachingOperationInvokerTests { ...@@ -114,7 +114,10 @@ class CachingOperationInvokerTests {
given(target.invoke(context)).willReturn(new Object()); given(target.invoke(context)).willReturn(new Object());
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L);
invoker.invoke(context); invoker.invoke(context);
Thread.sleep(55); long expired = System.currentTimeMillis() + 50;
while (System.currentTimeMillis() < expired) {
Thread.sleep(10);
}
invoker.invoke(context); invoker.invoke(context);
verify(target, times(2)).invoke(context); verify(target, times(2)).invoke(context);
} }
......
...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.logging; ...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.logging;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
...@@ -70,7 +71,7 @@ class LogFileWebEndpointTests { ...@@ -70,7 +71,7 @@ class LogFileWebEndpointTests {
this.environment.setProperty("logging.file.name", this.logFile.getAbsolutePath()); this.environment.setProperty("logging.file.name", this.logFile.getAbsolutePath());
Resource resource = this.endpoint.logFile(); Resource resource = this.endpoint.logFile();
assertThat(resource).isNotNull(); assertThat(resource).isNotNull();
assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)).isEqualTo("--TEST--"); assertThat(contentOf(resource)).isEqualTo("--TEST--");
} }
@Test @Test
...@@ -79,7 +80,7 @@ class LogFileWebEndpointTests { ...@@ -79,7 +80,7 @@ class LogFileWebEndpointTests {
this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); this.environment.setProperty("logging.file", this.logFile.getAbsolutePath());
Resource resource = this.endpoint.logFile(); Resource resource = this.endpoint.logFile();
assertThat(resource).isNotNull(); assertThat(resource).isNotNull();
assertThat(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)).isEqualTo("--TEST--"); assertThat(contentOf(resource)).isEqualTo("--TEST--");
} }
@Test @Test
...@@ -87,7 +88,13 @@ class LogFileWebEndpointTests { ...@@ -87,7 +88,13 @@ class LogFileWebEndpointTests {
LogFileWebEndpoint endpoint = new LogFileWebEndpoint(this.environment, this.logFile); LogFileWebEndpoint endpoint = new LogFileWebEndpoint(this.environment, this.logFile);
Resource resource = endpoint.logFile(); Resource resource = endpoint.logFile();
assertThat(resource).isNotNull(); 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);
}
} }
} }
...@@ -28,6 +28,8 @@ import org.apache.derby.jdbc.EmbeddedDriver; ...@@ -28,6 +28,8 @@ import org.apache.derby.jdbc.EmbeddedDriver;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.junit.jupiter.api.io.TempDir;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
...@@ -121,6 +123,7 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat ...@@ -121,6 +123,7 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat
} }
@Test @Test
@DisabledOnOs(OS.WINDOWS)
void inMemoryDerbyIsShutdown() throws Exception { void inMemoryDerbyIsShutdown() throws Exception {
ConfigurableApplicationContext context = getContext( ConfigurableApplicationContext context = getContext(
() -> createContext("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:test;create=true", () -> createContext("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:memory:test;create=true",
...@@ -132,6 +135,9 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat ...@@ -132,6 +135,9 @@ class DevToolsPooledDataSourceAutoConfigurationTests extends AbstractDevToolsDat
assertThatExceptionOfType(SQLException.class) assertThatExceptionOfType(SQLException.class)
.isThrownBy(() -> new EmbeddedDriver().connect("jdbc:derby:memory:test", new Properties())) .isThrownBy(() -> new EmbeddedDriver().connect("jdbc:derby:memory:test", new Properties()))
.satisfies((ex) -> assertThat(ex.getSQLState()).isEqualTo("XJ004")); .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()));
} }
} }
...@@ -29,6 +29,7 @@ import java.util.List; ...@@ -29,6 +29,7 @@ import java.util.List;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -72,6 +73,12 @@ class RestartClassLoaderTests { ...@@ -72,6 +73,12 @@ class RestartClassLoaderTests {
this.reloadClassLoader = new RestartClassLoader(this.parentClassLoader, urls, this.updatedFiles); 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 { private File createSampleJarFile(File tempDir) throws IOException {
File file = new File(tempDir, "sample.jar"); File file = new File(tempDir, "sample.jar");
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file)); JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
......
...@@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor.test; ...@@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor.test;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.SupportedSourceVersion;
...@@ -96,7 +97,9 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM ...@@ -96,7 +97,9 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
try { try {
File metadataFile = new File(this.outputLocation, "META-INF/spring-configuration-metadata.json"); File metadataFile = new File(this.outputLocation, "META-INF/spring-configuration-metadata.json");
if (metadataFile.isFile()) { if (metadataFile.isFile()) {
this.metadata = new JsonMarshaller().read(new FileInputStream(metadataFile)); try (InputStream input = new FileInputStream(metadataFile)) {
this.metadata = new JsonMarshaller().read(input);
}
} }
else { else {
this.metadata = new ConfigurationMetadata(); this.metadata = new ConfigurationMetadata();
......
...@@ -20,6 +20,7 @@ import java.io.File; ...@@ -20,6 +20,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.jar.JarFile;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -52,8 +53,10 @@ class MainClassFinderTests { ...@@ -52,8 +53,10 @@ class MainClassFinderTests {
void findMainClassInJar() throws Exception { void findMainClassInJar() throws Exception {
this.testJarFile.addClass("B.class", ClassWithMainMethod.class); this.testJarFile.addClass("B.class", ClassWithMainMethod.class);
this.testJarFile.addClass("A.class", ClassWithoutMainMethod.class); this.testJarFile.addClass("A.class", ClassWithoutMainMethod.class);
String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); try (JarFile jarFile = this.testJarFile.getJarFile()) {
assertThat(actual).isEqualTo("B"); String actual = MainClassFinder.findMainClass(jarFile, "");
assertThat(actual).isEqualTo("B");
}
} }
@Test @Test
...@@ -61,43 +64,52 @@ class MainClassFinderTests { ...@@ -61,43 +64,52 @@ class MainClassFinderTests {
this.testJarFile.addClass("a/b/c/D.class", ClassWithMainMethod.class); 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/c/E.class", ClassWithoutMainMethod.class);
this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class); this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class);
String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); try (JarFile jarFile = this.testJarFile.getJarFile()) {
assertThat(actual).isEqualTo("a.b.c.D"); String actual = MainClassFinder.findMainClass(jarFile, "");
assertThat(actual).isEqualTo("a.b.c.D");
}
} }
@Test @Test
void usesBreadthFirstJarSearch() throws Exception { void usesBreadthFirstJarSearch() throws Exception {
this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class);
this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class);
String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), ""); try (JarFile jarFile = this.testJarFile.getJarFile()) {
assertThat(actual).isEqualTo("a.B"); String actual = MainClassFinder.findMainClass(jarFile, "");
assertThat(actual).isEqualTo("a.B");
}
} }
@Test @Test
void findSingleJarSearch() throws Exception { void findSingleJarSearch() throws Exception {
this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class);
this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class);
assertThatIllegalStateException() try (JarFile jarFile = this.testJarFile.getJarFile()) {
.isThrownBy(() -> MainClassFinder.findSingleMainClass(this.testJarFile.getJarFile(), "")) assertThatIllegalStateException().isThrownBy(() -> MainClassFinder.findSingleMainClass(jarFile, ""))
.withMessageContaining( .withMessageContaining(
"Unable to find a single main class " + "from the following candidates [a.B, a.b.c.E]"); "Unable to find a single main class " + "from the following candidates [a.B, a.b.c.E]");
}
} }
@Test @Test
void findSingleJarSearchPrefersAnnotatedMainClass() throws Exception { void findSingleJarSearchPrefersAnnotatedMainClass() throws Exception {
this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class);
this.testJarFile.addClass("a/b/c/E.class", AnnotatedClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", AnnotatedClassWithMainMethod.class);
String mainClass = MainClassFinder.findSingleMainClass(this.testJarFile.getJarFile(), "", try (JarFile jarFile = this.testJarFile.getJarFile()) {
"org.springframework.boot.loader.tools.sample.SomeApplication"); String mainClass = MainClassFinder.findSingleMainClass(jarFile, "",
assertThat(mainClass).isEqualTo("a.b.c.E"); "org.springframework.boot.loader.tools.sample.SomeApplication");
assertThat(mainClass).isEqualTo("a.b.c.E");
}
} }
@Test @Test
void findMainClassInJarSubLocation() throws Exception { void findMainClassInJarSubLocation() throws Exception {
this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class);
this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class);
String actual = MainClassFinder.findMainClass(this.testJarFile.getJarFile(), "a/"); try (JarFile jarFile = this.testJarFile.getJarFile()) {
assertThat(actual).isEqualTo("B"); String actual = MainClassFinder.findMainClass(jarFile, "a/");
assertThat(actual).isEqualTo("B");
}
} }
...@@ -163,8 +175,10 @@ class MainClassFinderTests { ...@@ -163,8 +175,10 @@ class MainClassFinderTests {
this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class); this.testJarFile.addClass("a/b/F.class", ClassWithoutMainMethod.class);
this.testJarFile.addClass("a/b/G.class", ClassWithMainMethod.class); this.testJarFile.addClass("a/b/G.class", ClassWithMainMethod.class);
ClassNameCollector callback = new ClassNameCollector(); ClassNameCollector callback = new ClassNameCollector();
MainClassFinder.doWithMainClasses(this.testJarFile.getJarFile(), null, callback); try (JarFile jarFile = this.testJarFile.getJarFile()) {
assertThat(callback.getClassNames().toString()).isEqualTo("[a.b.G, a.b.c.D]"); MainClassFinder.doWithMainClasses(jarFile, null, callback);
assertThat(callback.getClassNames().toString()).isEqualTo("[a.b.G, a.b.c.D]");
}
} }
private static class ClassNameCollector implements MainClassCallback<Object> { private static class ClassNameCollector implements MainClassCallback<Object> {
......
...@@ -43,16 +43,25 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { ...@@ -43,16 +43,25 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
assertThat(archives).hasSize(2); assertThat(archives).hasSize(2);
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(), assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(),
new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL()); new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL());
for (Archive archive : archives) {
archive.close();
}
} }
@Test @Test
void archivedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception { void archivedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception {
File jarRoot = createJarArchive("archive.jar", "BOOT-INF"); File jarRoot = createJarArchive("archive.jar", "BOOT-INF");
JarLauncher launcher = new JarLauncher(new JarFileArchive(jarRoot)); try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
List<Archive> archives = launcher.getClassPathArchives(); JarLauncher launcher = new JarLauncher(archive);
assertThat(archives).hasSize(2); List<Archive> classPathArchives = launcher.getClassPathArchives();
assertThat(getUrls(archives)).containsOnly(new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"), assertThat(classPathArchives).hasSize(2);
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/")); 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();
}
}
} }
} }
...@@ -17,7 +17,10 @@ ...@@ -17,7 +17,10 @@
package org.springframework.boot.loader; package org.springframework.boot.loader;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -71,29 +74,37 @@ class LaunchedURLClassLoaderTests { ...@@ -71,29 +74,37 @@ class LaunchedURLClassLoaderTests {
void resolveFromNested() throws Exception { void resolveFromNested() throws Exception {
File file = new File(this.tempDir, "test.jar"); File file = new File(this.tempDir, "test.jar");
TestJarCreator.createTestJar(file); TestJarCreator.createTestJar(file);
JarFile jarFile = new JarFile(file); try (JarFile jarFile = new JarFile(file)) {
URL url = jarFile.getUrl(); URL url = jarFile.getUrl();
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null); try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) {
URL resource = loader.getResource("nested.jar!/3.dat"); URL resource = loader.getResource("nested.jar!/3.dat");
assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat");
assertThat(resource.openConnection().getInputStream().read()).isEqualTo(3); try (InputStream input = resource.openConnection().getInputStream()) {
assertThat(input.read()).isEqualTo(3);
}
}
}
} }
@Test @Test
void resolveFromNestedWhileThreadIsInterrupted() throws Exception { void resolveFromNestedWhileThreadIsInterrupted() throws Exception {
File file = new File(this.tempDir, "test.jar"); File file = new File(this.tempDir, "test.jar");
TestJarCreator.createTestJar(file); TestJarCreator.createTestJar(file);
JarFile jarFile = new JarFile(file); try (JarFile jarFile = new JarFile(file)) {
URL url = jarFile.getUrl(); URL url = jarFile.getUrl();
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null); try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) {
try { Thread.currentThread().interrupt();
Thread.currentThread().interrupt(); URL resource = loader.getResource("nested.jar!/3.dat");
URL resource = loader.getResource("nested.jar!/3.dat"); assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat");
assertThat(resource.toString()).isEqualTo(url + "nested.jar!/3.dat"); URLConnection connection = resource.openConnection();
assertThat(resource.openConnection().getInputStream().read()).isEqualTo(3); try (InputStream input = connection.getInputStream()) {
} assertThat(input.read()).isEqualTo(3);
finally { }
Thread.interrupted(); ((JarURLConnection) connection).getJarFile().close();
}
finally {
Thread.interrupted();
}
} }
} }
......
...@@ -312,7 +312,9 @@ class PropertiesLauncherTests { ...@@ -312,7 +312,9 @@ class PropertiesLauncherTests {
manifest.getMainAttributes().putValue("Loader-Path", "/foo.jar, /bar"); manifest.getMainAttributes().putValue("Loader-Path", "/foo.jar, /bar");
File manifestFile = new File(this.tempDir, "META-INF/MANIFEST.MF"); File manifestFile = new File(this.tempDir, "META-INF/MANIFEST.MF");
manifestFile.getParentFile().mkdirs(); manifestFile.getParentFile().mkdirs();
manifest.write(new FileOutputStream(manifestFile)); try (FileOutputStream output = new FileOutputStream(manifestFile)) {
manifest.write(output);
}
PropertiesLauncher launcher = new PropertiesLauncher(); PropertiesLauncher launcher = new PropertiesLauncher();
assertThat((List<String>) ReflectionTestUtils.getField(launcher, "paths")).containsExactly("/foo.jar", "/bar/"); assertThat((List<String>) ReflectionTestUtils.getField(launcher, "paths")).containsExactly("/foo.jar", "/bar/");
} }
......
...@@ -43,16 +43,25 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { ...@@ -43,16 +43,25 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
assertThat(archives).hasSize(2); assertThat(archives).hasSize(2);
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(), assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(),
new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL()); new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL());
for (Archive archive : archives) {
archive.close();
}
} }
@Test @Test
void archivedWarHasOnlyWebInfClassesAndContentsOWebInfLibOnClasspath() throws Exception { void archivedWarHasOnlyWebInfClassesAndContentsOWebInfLibOnClasspath() throws Exception {
File jarRoot = createJarArchive("archive.war", "WEB-INF"); File jarRoot = createJarArchive("archive.war", "WEB-INF");
WarLauncher launcher = new WarLauncher(new JarFileArchive(jarRoot)); try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
List<Archive> archives = launcher.getClassPathArchives(); WarLauncher launcher = new WarLauncher(archive);
assertThat(archives).hasSize(2); List<Archive> classPathArchives = launcher.getClassPathArchives();
assertThat(getUrls(archives)).containsOnly(new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"), assertThat(classPathArchives).hasSize(2);
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/")); 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();
}
}
} }
} }
...@@ -18,9 +18,6 @@ package org.springframework.boot.loader.archive; ...@@ -18,9 +18,6 @@ package org.springframework.boot.loader.archive;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Enumeration; import java.util.Enumeration;
...@@ -36,6 +33,7 @@ import org.junit.jupiter.api.io.TempDir; ...@@ -36,6 +33,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.loader.TestJarCreator; import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.archive.Archive.Entry; import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -81,22 +79,13 @@ class ExplodedArchiveTests { ...@@ -81,22 +79,13 @@ class ExplodedArchiveTests {
destination.mkdir(); destination.mkdir();
} }
else { else {
copy(jarFile.getInputStream(entry), new FileOutputStream(destination)); FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(destination));
} }
} }
this.archive = new ExplodedArchive(this.rootFolder); this.archive = new ExplodedArchive(this.rootFolder);
jarFile.close(); 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 @Test
void getManifest() throws Exception { void getManifest() throws Exception {
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1"); assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1");
...@@ -124,6 +113,7 @@ class ExplodedArchiveTests { ...@@ -124,6 +113,7 @@ class ExplodedArchiveTests {
Entry entry = getEntriesMap(this.archive).get("nested.jar"); Entry entry = getEntriesMap(this.archive).get("nested.jar");
Archive nested = this.archive.getNestedArchive(entry); Archive nested = this.archive.getNestedArchive(entry);
assertThat(nested.getUrl().toString()).isEqualTo(this.rootFolder.toURI() + "nested.jar"); assertThat(nested.getUrl().toString()).isEqualTo(this.rootFolder.toURI() + "nested.jar");
((JarFileArchive) nested).close();
} }
@Test @Test
......
...@@ -28,6 +28,7 @@ import java.util.jar.JarOutputStream; ...@@ -28,6 +28,7 @@ import java.util.jar.JarOutputStream;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -61,10 +62,18 @@ class JarFileArchiveTests { ...@@ -61,10 +62,18 @@ class JarFileArchiveTests {
setup(false); setup(false);
} }
@AfterEach
public void tearDown() throws Exception {
this.archive.close();
}
private void setup(boolean unpackNested) throws Exception { private void setup(boolean unpackNested) throws Exception {
this.rootJarFile = new File(this.tempDir, "root.jar"); this.rootJarFile = new File(this.tempDir, "root.jar");
this.rootJarFileUrl = this.rootJarFile.toURI().toString(); this.rootJarFileUrl = this.rootJarFile.toURI().toString();
TestJarCreator.createTestJar(this.rootJarFile, unpackNested); TestJarCreator.createTestJar(this.rootJarFile, unpackNested);
if (this.archive != null) {
this.archive.close();
}
this.archive = new JarFileArchive(this.rootJarFile); this.archive = new JarFileArchive(this.rootJarFile);
} }
...@@ -90,6 +99,7 @@ class JarFileArchiveTests { ...@@ -90,6 +99,7 @@ class JarFileArchiveTests {
Entry entry = getEntriesMap(this.archive).get("nested.jar"); Entry entry = getEntriesMap(this.archive).get("nested.jar");
Archive nested = this.archive.getNestedArchive(entry); Archive nested = this.archive.getNestedArchive(entry);
assertThat(nested.getUrl().toString()).isEqualTo("jar:" + this.rootJarFileUrl + "!/nested.jar!/"); assertThat(nested.getUrl().toString()).isEqualTo("jar:" + this.rootJarFileUrl + "!/nested.jar!/");
((JarFileArchive) nested).close();
} }
@Test @Test
...@@ -99,27 +109,36 @@ class JarFileArchiveTests { ...@@ -99,27 +109,36 @@ class JarFileArchiveTests {
Archive nested = this.archive.getNestedArchive(entry); Archive nested = this.archive.getNestedArchive(entry);
assertThat(nested.getUrl().toString()).startsWith("file:"); assertThat(nested.getUrl().toString()).startsWith("file:");
assertThat(nested.getUrl().toString()).endsWith("/nested.jar"); assertThat(nested.getUrl().toString()).endsWith("/nested.jar");
((JarFileArchive) nested).close();
} }
@Test @Test
void unpackedLocationsAreUniquePerArchive() throws Exception { void unpackedLocationsAreUniquePerArchive() throws Exception {
setup(true); setup(true);
Entry entry = getEntriesMap(this.archive).get("nested.jar"); 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); setup(true);
entry = getEntriesMap(this.archive).get("nested.jar"); entry = getEntriesMap(this.archive).get("nested.jar");
URL secondNested = this.archive.getNestedArchive(entry).getUrl(); Archive secondNested = this.archive.getNestedArchive(entry);
assertThat(secondNested).isNotEqualTo(firstNested); URL secondNestedUrl = secondNested.getUrl();
assertThat(secondNestedUrl).isNotEqualTo(firstNestedUrl);
((JarFileArchive) secondNested).close();
} }
@Test @Test
void unpackedLocationsFromSameArchiveShareSameParent() throws Exception { void unpackedLocationsFromSameArchiveShareSameParent() throws Exception {
setup(true); setup(true);
File nested = new File( Archive nestedArchive = this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar"));
this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar")).getUrl().toURI()); File nested = new File(nestedArchive.getUrl().toURI());
File anotherNested = new File( Archive anotherNestedArchive = this.archive
this.archive.getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar")).getUrl().toURI()); .getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar"));
File anotherNested = new File(anotherNestedArchive.getUrl().toURI());
assertThat(nested.getParent()).isEqualTo(anotherNested.getParent()); assertThat(nested.getParent()).isEqualTo(anotherNested.getParent());
((JarFileArchive) nestedArchive).close();
((JarFileArchive) anotherNestedArchive).close();
} }
@Test @Test
...@@ -147,10 +166,11 @@ class JarFileArchiveTests { ...@@ -147,10 +166,11 @@ class JarFileArchiveTests {
output.closeEntry(); output.closeEntry();
output.close(); output.close();
JarFileArchive jarFileArchive = new JarFileArchive(file); JarFileArchive jarFileArchive = new JarFileArchive(file);
assertThatIllegalStateException() assertThatIllegalStateException().isThrownBy(() -> {
.isThrownBy( Archive archive = jarFileArchive.getNestedArchive(getEntriesMap(jarFileArchive).get("nested/zip64.jar"));
() -> jarFileArchive.getNestedArchive(getEntriesMap(jarFileArchive).get("nested/zip64.jar"))) ((JarFileArchive) archive).close();
.withMessageContaining("Failed to get nested archive for entry nested/zip64.jar"); }).withMessageContaining("Failed to get nested archive for entry nested/zip64.jar");
jarFileArchive.close();
} }
private byte[] writeZip64Jar() throws IOException { private byte[] writeZip64Jar() throws IOException {
......
...@@ -17,10 +17,12 @@ ...@@ -17,10 +17,12 @@
package org.springframework.boot.loader.jar; package org.springframework.boot.loader.jar;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -40,7 +42,7 @@ class CentralDirectoryParserTests { ...@@ -40,7 +42,7 @@ class CentralDirectoryParserTests {
private File jarFile; private File jarFile;
private RandomAccessData jarData; private RandomAccessDataFile jarData;
@BeforeEach @BeforeEach
public void setup(@TempDir File tempDir) throws Exception { public void setup(@TempDir File tempDir) throws Exception {
...@@ -49,6 +51,11 @@ class CentralDirectoryParserTests { ...@@ -49,6 +51,11 @@ class CentralDirectoryParserTests {
this.jarData = new RandomAccessDataFile(this.jarFile); this.jarData = new RandomAccessDataFile(this.jarFile);
} }
@AfterEach
public void tearDown() throws IOException {
this.jarData.close();
}
@Test @Test
void visitsInOrder() throws Exception { void visitsInOrder() throws Exception {
MockCentralDirectoryVisitor visitor = new MockCentralDirectoryVisitor(); MockCentralDirectoryVisitor visitor = new MockCentralDirectoryVisitor();
......
...@@ -22,6 +22,7 @@ import java.net.URL; ...@@ -22,6 +22,7 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.loader.TestJarCreator; import org.springframework.boot.loader.TestJarCreator;
...@@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@ExtendWith(JarUrlProtocolHandler.class)
class HandlerTests { class HandlerTests {
private final Handler handler = new Handler(); private final Handler handler = new Handler();
...@@ -157,6 +159,7 @@ class HandlerTests { ...@@ -157,6 +159,7 @@ class HandlerTests {
URLConnection connection = new URL(null, "jar:file:" + testJar.getAbsolutePath() + "!/nested.jar!/", URLConnection connection = new URL(null, "jar:file:" + testJar.getAbsolutePath() + "!/nested.jar!/",
this.handler).openConnection(); this.handler).openConnection();
assertThat(connection).isInstanceOf(JarURLConnection.class); assertThat(connection).isInstanceOf(JarURLConnection.class);
((JarURLConnection) connection).getJarFile().close();
URLConnection jdkConnection = new URL(null, "jar:file:file:" + testJar.getAbsolutePath() + "!/nested.jar!/", URLConnection jdkConnection = new URL(null, "jar:file:file:" + testJar.getAbsolutePath() + "!/nested.jar!/",
this.handler).openConnection(); this.handler).openConnection();
assertThat(jdkConnection).isNotInstanceOf(JarURLConnection.class); assertThat(jdkConnection).isNotInstanceOf(JarURLConnection.class);
......
...@@ -19,8 +19,10 @@ package org.springframework.boot.loader.jar; ...@@ -19,8 +19,10 @@ package org.springframework.boot.loader.jar;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -51,6 +53,11 @@ class JarURLConnectionTests { ...@@ -51,6 +53,11 @@ class JarURLConnectionTests {
this.jarFile = new JarFile(this.rootJarFile); this.jarFile = new JarFile(this.rootJarFile);
} }
@AfterEach
public void tearDown() throws Exception {
this.jarFile.close();
}
@Test @Test
void connectionToRootUsingAbsoluteUrl() throws Exception { void connectionToRootUsingAbsoluteUrl() throws Exception {
URL url = new URL("jar:file:" + getAbsolutePath() + "!/"); URL url = new URL("jar:file:" + getAbsolutePath() + "!/");
...@@ -66,97 +73,123 @@ class JarURLConnectionTests { ...@@ -66,97 +73,123 @@ class JarURLConnectionTests {
@Test @Test
void connectionToEntryUsingAbsoluteUrl() throws Exception { void connectionToEntryUsingAbsoluteUrl() throws Exception {
URL url = new URL("jar:file:" + getAbsolutePath() + "!/1.dat"); URL url = new URL("jar:file:" + getAbsolutePath() + "!/1.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 }));
}
} }
@Test @Test
void connectionToEntryUsingRelativeUrl() throws Exception { void connectionToEntryUsingRelativeUrl() throws Exception {
URL url = new URL("jar:file:" + getRelativePath() + "!/1.dat"); URL url = new URL("jar:file:" + getRelativePath() + "!/1.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 }));
}
} }
@Test @Test
void connectionToEntryUsingAbsoluteUrlWithFileColonSlashSlashPrefix() throws Exception { void connectionToEntryUsingAbsoluteUrlWithFileColonSlashSlashPrefix() throws Exception {
URL url = new URL("jar:file:/" + getAbsolutePath() + "!/1.dat"); URL url = new URL("jar:file:/" + getAbsolutePath() + "!/1.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 1 }));
}
} }
@Test @Test
void connectionToEntryUsingAbsoluteUrlForNestedEntry() throws Exception { void connectionToEntryUsingAbsoluteUrlForNestedEntry() throws Exception {
URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); try (InputStream input = connection.getInputStream()) {
assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
connection.getJarFile().close();
} }
@Test @Test
void connectionToEntryUsingRelativeUrlForNestedEntry() throws Exception { void connectionToEntryUsingRelativeUrlForNestedEntry() throws Exception {
URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); try (InputStream input = connection.getInputStream()) {
assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
connection.getJarFile().close();
} }
@Test @Test
void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile() throws Exception { void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile() throws Exception {
URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat"); URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat");
JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThat(JarURLConnection.get(url, nested).getInputStream()) try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
}
} }
@Test @Test
void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile() throws Exception { void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile() throws Exception {
URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat");
JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThat(JarURLConnection.get(url, nested).getInputStream()) try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
}
} }
@Test @Test
void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() throws Exception { void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() throws Exception {
URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()),
"/3.dat"); "/3.dat");
JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThat(JarURLConnection.get(url, nested).getInputStream()) try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
}
} }
@Test @Test
void connectionToEntryWithSpaceNestedEntry() throws Exception { void connectionToEntryWithSpaceNestedEntry() throws Exception {
URL url = new URL("jar:file:" + getRelativePath() + "!/space nested.jar!/3.dat"); URL url = new URL("jar:file:" + getRelativePath() + "!/space nested.jar!/3.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); try (InputStream input = connection.getInputStream()) {
assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
connection.getJarFile().close();
} }
@Test @Test
void connectionToEntryWithEncodedSpaceNestedEntry() throws Exception { void connectionToEntryWithEncodedSpaceNestedEntry() throws Exception {
URL url = new URL("jar:file:" + getRelativePath() + "!/space%20nested.jar!/3.dat"); URL url = new URL("jar:file:" + getRelativePath() + "!/space%20nested.jar!/3.dat");
assertThat(JarURLConnection.get(url, this.jarFile).getInputStream()) JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 })); try (InputStream input = connection.getInputStream()) {
assertThat(input).hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
connection.getJarFile().close();
} }
@Test @Test
void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() throws Exception { void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() throws Exception {
URL url = new URL("jar:file:" + getAbsolutePath() + "!/w.jar!/3.dat"); URL url = new URL("jar:file:" + getAbsolutePath() + "!/w.jar!/3.dat");
JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThatExceptionOfType(FileNotFoundException.class) assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(JarURLConnection.get(url, nested)::getInputStream); .isThrownBy(JarURLConnection.get(url, nested)::getInputStream);
}
} }
@Test @Test
void getContentLengthReturnsLengthOfUnderlyingEntry() throws Exception { void getContentLengthReturnsLengthOfUnderlyingEntry() throws Exception {
URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat");
"/3.dat"); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThat(url.openConnection().getContentLength()).isEqualTo(1); JarURLConnection connection = JarURLConnection.get(url, nested);
assertThat(connection.getContentLength()).isEqualTo(1);
}
} }
@Test @Test
void getContentLengthLongReturnsLengthOfUnderlyingEntry() throws Exception { void getContentLengthLongReturnsLengthOfUnderlyingEntry() throws Exception {
URL url = new URL(new URL("jar", null, -1, "file:" + getAbsolutePath() + "!/nested.jar!/", new Handler()), URL url = new URL("jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat");
"/3.dat"); try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
assertThat(url.openConnection().getContentLengthLong()).isEqualTo(1); JarURLConnection connection = JarURLConnection.get(url, nested);
assertThat(connection.getContentLengthLong()).isEqualTo(1);
}
} }
@Test @Test
......
/*
* 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<File, JarFile> rootFileCache = ((SoftReference<Map<File, JarFile>>) ReflectionTestUtils
.getField(Handler.class, "rootFileCache")).get();
if (rootFileCache != null) {
for (JarFile rootJarFile : rootFileCache.values()) {
rootJarFile.close();
}
rootFileCache.clear();
}
}
}
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
...@@ -127,34 +129,43 @@ class ApplicationPidFileWriterTests { ...@@ -127,34 +129,43 @@ class ApplicationPidFileWriterTests {
@Test @Test
void continueWhenPidFileIsReadOnly() throws Exception { void continueWhenPidFileIsReadOnly() throws Exception {
File file = new File(this.tempDir, "pid"); withReadOnlyPidFile((file) -> {
file.createNewFile(); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
file.setReadOnly(); listener.onApplicationEvent(EVENT);
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); assertThat(contentOf(file)).isEmpty();
listener.onApplicationEvent(EVENT); });
assertThat(contentOf(file)).isEmpty();
} }
@Test @Test
void throwWhenPidFileIsReadOnly() throws Exception { void throwWhenPidFileIsReadOnly() throws Exception {
File file = new File(this.tempDir, "pid"); withReadOnlyPidFile((file) -> {
file.createNewFile(); System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true");
file.setReadOnly(); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true"); assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(EVENT))
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); .withMessageContaining("Cannot create pid file");
assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(EVENT)) });
.withMessageContaining("Cannot create pid file");
} }
@Test @Test
void throwWhenPidFileIsReadOnlyWithSpring() throws Exception { 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<File> consumer) throws IOException {
File file = new File(this.tempDir, "pid"); File file = new File(this.tempDir, "pid");
file.createNewFile(); file.createNewFile();
file.setReadOnly(); file.setReadOnly();
SpringApplicationEvent event = createPreparedEvent("spring.pid.fail-on-write-error", "true"); try {
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); consumer.accept(file);
assertThatIllegalStateException().isThrownBy(() -> listener.onApplicationEvent(event)) }
.withMessageContaining("Cannot create pid file"); finally {
file.setWritable(true);
}
} }
private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) { private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) {
......
...@@ -75,8 +75,16 @@ class TomcatEmbeddedWebappClassLoaderTests { ...@@ -75,8 +75,16 @@ class TomcatEmbeddedWebappClassLoaderTests {
resources.start(); resources.start();
classLoader.setResources(resources); classLoader.setResources(resources);
classLoader.start(); classLoader.start();
consumer.accept(classLoader); try {
consumer.accept(classLoader);
}
finally {
classLoader.stop();
classLoader.close();
resources.stop();
}
} }
parent.close();
} }
private String webInfClassesUrlString(File war) { private String webInfClassesUrlString(File war) {
......
...@@ -42,10 +42,11 @@ class JarResourceManagerTests { ...@@ -42,10 +42,11 @@ class JarResourceManagerTests {
@BeforeEach @BeforeEach
public void createJar(@TempDir File tempDir) throws IOException { public void createJar(@TempDir File tempDir) throws IOException {
File jar = new File(tempDir, "test.jar"); File jar = new File(tempDir, "test.jar");
JarOutputStream out = new JarOutputStream(new FileOutputStream(jar)); try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) {
out.putNextEntry(new ZipEntry("hello.txt")); out.putNextEntry(new ZipEntry("hello.txt"));
out.write("hello".getBytes()); out.write("hello".getBytes());
out.close(); out.close();
}
this.resourceManager = new JarResourceManager(jar); this.resourceManager = new JarResourceManager(jar);
} }
......
...@@ -18,6 +18,7 @@ package sample.parent.consumer; ...@@ -18,6 +18,7 @@ package sample.parent.consumer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -94,7 +95,9 @@ class SampleIntegrationParentApplicationTests { ...@@ -94,7 +95,9 @@ class SampleIntegrationParentApplicationTests {
private String readResources(Resource[] resources) throws IOException { private String readResources(Resource[] resources) throws IOException {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (Resource resource : resources) { 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(); return builder.toString();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment