From 9543aa9ed36ddd6e4c6af5cc50f50bae2d2eac73 Mon Sep 17 00:00:00 2001 From: Alexander Kriegisch Date: Thu, 18 Jan 2024 19:04:21 +0700 Subject: [PATCH 1/3] Fix failing Maven build on Windows Two parametrised test iterations using symlinks on native file systems were failing on Windows, because there we do not have UNIX-style symlinks. Skipping the whole test using @DisabledOnOs would have been an option, but a suboptimal one, because then the iteration testing symlinks on the in-memory FS would also have been skipped. Therefore, an assumption is used: Assumptions.assumeFalse( useDefault && OS.current().equals(OS.WINDOWS), "skip symbolic link test on Windows default file system" ); --- .../memoryfilesystem/FileSystemCompatibilityTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java b/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java index 3d0efce..7fcf27e 100644 --- a/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java +++ b/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java @@ -40,7 +40,9 @@ import java.util.List; import java.util.stream.Stream; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -277,6 +279,10 @@ void regression93(boolean useDefault) { @CompatibilityTest void newDirectoryStreamFollowSymlinks(boolean useDefault) throws IOException { + Assumptions.assumeFalse( + useDefault && OS.current().equals(OS.WINDOWS), + "skip symbolic link test on Windows default file system" + ); FileSystem fileSystem = this.getFileSystem(useDefault); Path target = fileSystem.getPath("target"); if (!useDefault) { @@ -326,6 +332,11 @@ void manyDots(boolean useDefault) throws IOException { @CompatibilityTest void relativeSymlinks(boolean useDefault) throws IOException { + Assumptions.assumeFalse( + useDefault && OS.current().equals(OS.WINDOWS), + "skip symbolic link test on Windows default file system" + ); + FileSystem fileSystem = this.getFileSystem(useDefault); Path target = fileSystem.getPath("target"); if (!useDefault) { From e2e0dbc21f4a03c82f021a885fecc7ba28c83afd Mon Sep 17 00:00:00 2001 From: Alexander Kriegisch Date: Thu, 18 Jan 2024 19:15:29 +0700 Subject: [PATCH 2/3] Conditionally enable nested zip tests depending on JDK version Since JDK 12, ZipFileSystem technically supports nested zips, albeit only when using Path, not URI parameters. The corresponding OpenJDK commit is https://github.com/openjdk/jdk/commit/196c20c0d1. Unfortunately, only since JDK 13 there is an overloaded method FileSystems.newFileSystem(Path, Map), which also permits to create a new nested JAR. In order to stay JDK 8 compatible, however, we cannot use that method. Hence, the tests still use the workaround to create empty zip files via JarOutputStream. But at least it works, and we can conditionally enable two more tests using: @EnabledForJreRange(min = JRE.JAVA_12) One more test involving URIs for files inside zip files (non-nested ones only!) can be activated as of JDK 9+, because the problem was fixed since then. --- .../ZipFileSystemInteroperabilityTest.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/github/marschall/memoryfilesystem/ZipFileSystemInteroperabilityTest.java b/src/test/java/com/github/marschall/memoryfilesystem/ZipFileSystemInteroperabilityTest.java index d86ef00..75adb4f 100644 --- a/src/test/java/com/github/marschall/memoryfilesystem/ZipFileSystemInteroperabilityTest.java +++ b/src/test/java/com/github/marschall/memoryfilesystem/ZipFileSystemInteroperabilityTest.java @@ -19,14 +19,19 @@ import java.util.Map; import java.util.jar.JarOutputStream; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; class ZipFileSystemInteroperabilityTest { private static final String FS_URI = "jar:"; + // Avoid casts in FileSystems.newFileSystem(Path, ClassLoader) + private static final ClassLoader NULL_CLASS_LOADER = null; + private static final Map CREATE_ENV = Collections.singletonMap("create", "false"); + @RegisterExtension final FileSystemExtension extension = new FileSystemExtension(); @@ -43,25 +48,21 @@ void createZipFileSystem() throws IOException { } @Test - @Disabled("broken") + @EnabledForJreRange(min = JRE.JAVA_12, disabledReason = "nested zips only available on JDK 12+") void createNestedZips() throws IOException { FileSystem memoryFileSystem = this.extension.getFileSystem(); - Map env = Collections.singletonMap("create", "false"); Path outerZip = memoryFileSystem.getPath("/file.zip"); try (OutputStream stream = new JarOutputStream(Files.newOutputStream(outerZip, CREATE_NEW, WRITE))) { // nothing, just create an empty jar } - URI uri = URI.create(FS_URI + outerZip.toUri()); - try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) { + try (FileSystem zipfs = FileSystems.newFileSystem(outerZip, NULL_CLASS_LOADER)) { Path innerZip = zipfs.getPath("hello.zip"); try (OutputStream stream = new JarOutputStream(Files.newOutputStream(innerZip, CREATE_NEW, WRITE))) { // nothing, just create an empty jar } - Map env2 = Collections.singletonMap("create", "false"); // locate file system by using the syntax // defined in java.net.JarURLConnection - URI uri2 = URI.create(FS_URI + innerZip.toUri()); - try (FileSystem zipfs2 = FileSystems.newFileSystem(uri2, env2)) { + try (FileSystem zipfs2 = FileSystems.newFileSystem(innerZip, NULL_CLASS_LOADER)) { try (BufferedWriter writer = Files.newBufferedWriter(zipfs2.getPath("hello.txt"), US_ASCII, CREATE_NEW, WRITE)) { writer.write("world"); } @@ -70,7 +71,7 @@ void createNestedZips() throws IOException { } @Test - @Disabled("broken upstream") + @EnabledForJreRange(min = JRE.JAVA_9, disabledReason = "zip FS path to URI conversion works on JDK 9+") void jarToUriRegression() throws IOException { Path jarFolder = Files.createTempDirectory("jartest"); try { @@ -97,9 +98,8 @@ void jarToUriRegressionFixed() throws IOException { // nothing, just create an empty jar } try { - Map env = Collections.singletonMap("create", "false"); URI uri = URI.create(FS_URI + jarFile.toUri()); - try (FileSystem jarfs = FileSystems.newFileSystem(uri, env)) { + try (FileSystem jarfs = FileSystems.newFileSystem(uri, CREATE_ENV)) { Path p = jarfs.getPath("hello.txt"); assertNotNull(Paths.get(p.toUri())); } @@ -109,23 +109,19 @@ void jarToUriRegressionFixed() throws IOException { } @Test - @Disabled("broken upstream") - void nestesJarsRegression() throws IOException { + @EnabledForJreRange(min = JRE.JAVA_12, disabledReason = "nested zips only available on JDK 12+") + void nestedJarsRegression() throws IOException { Path outerJar = Files.createTempFile("outer", ".jar"); try (OutputStream stream = new JarOutputStream(Files.newOutputStream(outerJar))) { // nothing, just create an empty jar } try { - Map outerEnv = Collections.singletonMap("create", "false"); - URI outerUri = URI.create(FS_URI + outerJar.toUri()); - try (FileSystem jarfs = FileSystems.newFileSystem(outerUri, outerEnv)) { + try (FileSystem jarfs = FileSystems.newFileSystem(outerJar, NULL_CLASS_LOADER)) { Path innerJar = jarfs.getPath("inner.jar"); try (OutputStream stream = new JarOutputStream(Files.newOutputStream(innerJar, CREATE_NEW, WRITE))) { // nothing, just create an empty jar } - Map innerEnv = Collections.singletonMap("create", "false"); - URI innerUri = URI.create(FS_URI + innerJar.toUri()); - try (FileSystem zipfs2 = FileSystems.newFileSystem(innerUri, innerEnv)) { + try (FileSystem zipfs2 = FileSystems.newFileSystem(innerJar, NULL_CLASS_LOADER)) { try (BufferedWriter writer = Files.newBufferedWriter(zipfs2.getPath("hello.txt"), US_ASCII, CREATE_NEW, WRITE)) { writer.write("world"); } From 03b4104625201739f90e7b5f2e68af4fa292bb96 Mon Sep 17 00:00:00 2001 From: Alexander Kriegisch Date: Sat, 3 Feb 2024 18:42:29 +0700 Subject: [PATCH 3/3] Permit symlink Windows tests, if user is admin --- pom.xml | 6 +-- .../memoryfilesystem/AdminChecker.java | 41 +++++++++++++++++++ .../FileSystemCompatibilityTest.java | 8 ++-- 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/github/marschall/memoryfilesystem/AdminChecker.java diff --git a/pom.xml b/pom.xml index 75e3df0..0ec8511 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ repo - + @@ -360,7 +360,7 @@ - + true @@ -433,7 +433,7 @@ com.github.marschall.memoryfilesystem com.github.marschall.memoryfilesystem,com.github.marschall.memoryfilesystem.memory - java.annotation,jakarta.annotation + java.annotation,jakarta.annotation,java.prefs java.nio.file.spi.FileSystemProvider diff --git a/src/test/java/com/github/marschall/memoryfilesystem/AdminChecker.java b/src/test/java/com/github/marschall/memoryfilesystem/AdminChecker.java new file mode 100644 index 0000000..3d32598 --- /dev/null +++ b/src/test/java/com/github/marschall/memoryfilesystem/AdminChecker.java @@ -0,0 +1,41 @@ +package com.github.marschall.memoryfilesystem; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.prefs.Preferences; + +/** + * Checks if the current user is the local administrator + *

+ * Adapted from this Stack Overflow answer. + */ +public class AdminChecker { + public static final boolean IS_ADMIN = isRunningAsAdministrator(); + + private static boolean isRunningAsAdministrator() { + Preferences systemPrefs = Preferences.systemRoot(); + synchronized (System.err) { + PrintStream stdErrOriginal = System.err; + try (PrintStream stdErrMock = new PrintStream(new MockOutputStream())) { + System.setErr(stdErrMock); + try { + systemPrefs.put("foo", "bar"); // SecurityException on Windows + systemPrefs.remove("foo"); + systemPrefs.flush(); // BackingStoreException on Linux + return true; + } + catch (Exception exception) { + return false; + } + } + finally { + System.setErr(stdErrOriginal); + } + } + } + + private static class MockOutputStream extends OutputStream { + @Override + public void write(int b) {} + } +} diff --git a/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java b/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java index 7fcf27e..9198766 100644 --- a/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java +++ b/src/test/java/com/github/marschall/memoryfilesystem/FileSystemCompatibilityTest.java @@ -280,8 +280,8 @@ void regression93(boolean useDefault) { @CompatibilityTest void newDirectoryStreamFollowSymlinks(boolean useDefault) throws IOException { Assumptions.assumeFalse( - useDefault && OS.current().equals(OS.WINDOWS), - "skip symbolic link test on Windows default file system" + useDefault && OS.current().equals(OS.WINDOWS) && !AdminChecker.IS_ADMIN, + "skip symbolic link test on Windows default file system, if not admin" ); FileSystem fileSystem = this.getFileSystem(useDefault); Path target = fileSystem.getPath("target"); @@ -333,8 +333,8 @@ void manyDots(boolean useDefault) throws IOException { @CompatibilityTest void relativeSymlinks(boolean useDefault) throws IOException { Assumptions.assumeFalse( - useDefault && OS.current().equals(OS.WINDOWS), - "skip symbolic link test on Windows default file system" + useDefault && OS.current().equals(OS.WINDOWS) && !AdminChecker.IS_ADMIN, + "skip symbolic link test on Windows default file system, if not admin" ); FileSystem fileSystem = this.getFileSystem(useDefault);