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.
pull/17132/head
Andy Wilkinson 6 years ago
parent c56fbf8c3d
commit cffc870fd6

@ -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--");
}
});
}

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

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

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

@ -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));

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

@ -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<Object> {

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

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

@ -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<String>) ReflectionTestUtils.getField(launcher, "paths")).containsExactly("/foo.jar", "/bar/");
}

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

@ -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

@ -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 {

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

@ -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);

@ -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"));
Enumeration<java.util.jar.JarEntry> 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);
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());
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) {
Enumeration<java.util.jar.JarEntry> 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);
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/"));
Enumeration<java.util.jar.JarEntry> entries = nestedJarFile.entries();
assertThat(entries.nextElement().getName()).isEqualTo("9.dat");
assertThat(entries.hasMoreElements()).isFalse();
InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("9.dat"));
assertThat(inputStream.read()).isEqualTo(9);
assertThat(inputStream.read()).isEqualTo(-1);
try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("d/"))) {
Enumeration<java.util.jar.JarEntry> 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);
}
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() {

@ -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

@ -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<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 @@
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<File> 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) {

@ -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) {

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

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

Loading…
Cancel
Save