From a489cc4ee7269e45b3939b3d5601aadee5876140 Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Fri, 11 Jan 2019 15:35:02 -0600 Subject: [PATCH] Move Windows util code into test sources Signed-off-by: Matt Sicker --- core/src/main/java/hudson/Util.java | 23 --------- core/src/test/java/hudson/FilePathTest.java | 3 +- .../RemoveWindowsDirectoryJunctionTest.java | 4 +- core/src/test/java/hudson/UtilTest.java | 5 +- .../java/hudson/os/WindowsUtil.java} | 50 ++++++++++++++----- .../os/WindowsUtilTest.java} | 12 +++-- 6 files changed, 53 insertions(+), 44 deletions(-) rename core/src/{main/java/jenkins/util/os/windows/WindowsCommandLineFormatter.java => test/java/hudson/os/WindowsUtil.java} (60%) rename core/src/test/java/{jenkins/util/os/windows/WindowsCommandLineFormatterTest.java => hudson/os/WindowsUtilTest.java} (87%) diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index d754fb10fe..df80ca2ff3 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -30,7 +30,6 @@ import hudson.util.VariableResolver; import jenkins.util.SystemProperties; import jenkins.util.io.PathRemover; -import jenkins.util.os.windows.WindowsCommandLineFormatter; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; @@ -1277,28 +1276,6 @@ public class Util { } } - /** - * Creates an NTFS junction point if supported. Similar to symbolic links, NTFS provides junction points which - * provide different features than symbolic links. This method is primarily useful for unit testing on Windows. - * @param junction NTFS junction point to create - * @param target target directory to junction - * @return the newly created junction point - * @throws IOException if the call to mklink exits with a non-zero status code - * @throws InterruptedException if the call to mklink is interrupted before completing - * @throws AssertionError if this method is called on a non-Windows platform - */ - static @Nonnull File createJunction(@Nonnull File junction, @Nonnull File target) throws IOException, InterruptedException { - if (!Functions.isWindows()) throw new AssertionError("Cannot create junctions on non-Windows platforms"); - String command = "cmd.exe /C mklink /J " + - WindowsCommandLineFormatter.quoteArgumentForCmd(junction.getAbsolutePath()) + - ' ' + - WindowsCommandLineFormatter.quoteArgumentForCmd(target.getAbsolutePath()); - Process process = Runtime.getRuntime().exec(command); - int result = process.waitFor(); - if (result != 0) throw new IOException("Command `" + command + "` failed with status code " + result); - return junction; - } - /** * Encodes the URL by RFC 2396. * diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java index cf4e163b9d..b40d0a37d4 100644 --- a/core/src/test/java/hudson/FilePathTest.java +++ b/core/src/test/java/hudson/FilePathTest.java @@ -26,6 +26,7 @@ package hudson; import hudson.FilePath.TarCompression; import hudson.model.TaskListener; import hudson.os.PosixAPI; +import hudson.os.WindowsUtil; import hudson.remoting.VirtualChannel; import hudson.util.NullStream; import hudson.util.StreamTaskListener; @@ -836,7 +837,7 @@ public class FilePathTest { Path targetDir = temp.newFolder("targetDir").toPath(); Path targetContents = Files.createFile(targetDir.resolve("contents.txt")); Path toDelete = temp.newFolder("toDelete").toPath(); - File junction = Util.createJunction(toDelete.resolve("junction").toFile(), targetDir.toFile()); + File junction = WindowsUtil.createJunction(toDelete.resolve("junction").toFile(), targetDir.toFile()); Files.createFile(toDelete.resolve("foo")); Files.createFile(toDelete.resolve("bar")); FilePath f = new FilePath(toDelete.toFile()); diff --git a/core/src/test/java/hudson/RemoveWindowsDirectoryJunctionTest.java b/core/src/test/java/hudson/RemoveWindowsDirectoryJunctionTest.java index dc44db7974..de296e634b 100644 --- a/core/src/test/java/hudson/RemoveWindowsDirectoryJunctionTest.java +++ b/core/src/test/java/hudson/RemoveWindowsDirectoryJunctionTest.java @@ -8,6 +8,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import java.io.File; + +import hudson.os.WindowsUtil; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -32,7 +34,7 @@ public class RemoveWindowsDirectoryJunctionTest { File subdir1 = tmp.newFolder("notJunction"); File f1 = new File(subdir1, "testfile1.txt"); assertTrue("Unable to create temporary file in notJunction directory", f1.createNewFile()); - File j1 = Util.createJunction(tmp.getRoot(), subdir1); + File j1 = WindowsUtil.createJunction(tmp.getRoot(), subdir1); Util.deleteRecursive(j1); assertFalse("Windows Junction should have been removed", j1.exists()); assertTrue("Contents of Windows Junction should not be removed", f1.exists()); diff --git a/core/src/test/java/hudson/UtilTest.java b/core/src/test/java/hudson/UtilTest.java index cb19aa7484..8662fc5631 100644 --- a/core/src/test/java/hudson/UtilTest.java +++ b/core/src/test/java/hudson/UtilTest.java @@ -43,6 +43,7 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.*; +import hudson.os.WindowsUtil; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Assume; @@ -262,7 +263,7 @@ public class UtilTest { Assume.assumeTrue("Uses Windows-specific features", Functions.isWindows()); File targetDir = tmp.newFolder("targetDir"); File d = tmp.newFolder("dir"); - File junction = Util.createJunction(new File(d, "junction"), targetDir); + File junction = WindowsUtil.createJunction(new File(d, "junction"), targetDir); assertTrue(Util.isSymlink(junction)); } @@ -274,7 +275,7 @@ public class UtilTest { File file = new File(targetDir, "test-file"); new FilePath(file).touch(System.currentTimeMillis()); File dir = tmp.newFolder(); - File junction = Util.createJunction(new File(dir, "junction"), targetDir); + File junction = WindowsUtil.createJunction(new File(dir, "junction"), targetDir); assertTrue(Util.isSymlink(junction)); assertFalse(Util.isSymlink(file)); diff --git a/core/src/main/java/jenkins/util/os/windows/WindowsCommandLineFormatter.java b/core/src/test/java/hudson/os/WindowsUtil.java similarity index 60% rename from core/src/main/java/jenkins/util/os/windows/WindowsCommandLineFormatter.java rename to core/src/test/java/hudson/os/WindowsUtil.java index 4958ff2b12..56a1dd13d4 100644 --- a/core/src/main/java/jenkins/util/os/windows/WindowsCommandLineFormatter.java +++ b/core/src/test/java/hudson/os/WindowsUtil.java @@ -22,24 +22,21 @@ * THE SOFTWARE. */ -package jenkins.util.os.windows; +package hudson.os; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; +import hudson.Functions; +import org.apache.commons.io.IOUtils; import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; import java.util.regex.Pattern; +import java.util.stream.Collectors; -/** - * Provides formatting utilities for Windows command line processes. - * - * @see Everyone quotes command line arguments the wrong way - */ -@Restricted(NoExternalUse.class) -public final class WindowsCommandLineFormatter { - private WindowsCommandLineFormatter() { - } +import static org.junit.Assert.assertTrue; +public class WindowsUtil { /** * Quotes an argument while escaping special characters interpreted by CreateProcess. */ @@ -83,4 +80,33 @@ public final class WindowsCommandLineFormatter { return CMD_METACHARS.matcher(quoteArgument(argument)).replaceAll("^$0"); } + /** + * Executes a command and arguments using {@code cmd.exe /C ...}. + */ + public static @Nonnull Process execCmd(String... argv) throws IOException { + String command = Arrays.stream(argv).map(WindowsUtil::quoteArgumentForCmd).collect(Collectors.joining(" ")); + return Runtime.getRuntime().exec(new String[]{"cmd.exe", "/C", command}); + } + + /** + * Creates an NTFS junction point if supported. Similar to symbolic links, NTFS provides junction points which + * provide different features than symbolic links. + * @param junction NTFS junction point to create + * @param target target directory to junction + * @return the newly created junction point + * @throws IOException if the call to mklink exits with a non-zero status code + * @throws InterruptedException if the call to mklink is interrupted before completing + * @throws AssertionError if this method is called on a non-Windows platform + */ + public static @Nonnull File createJunction(@Nonnull File junction, @Nonnull File target) throws IOException, InterruptedException { + assertTrue(Functions.isWindows()); + Process mklink = execCmd("mklink", "/J", junction.getAbsolutePath(), target.getAbsolutePath()); + int result = mklink.waitFor(); + if (result != 0) { + String stderr = IOUtils.toString(mklink.getErrorStream()); + String stdout = IOUtils.toString(mklink.getInputStream()); + throw new IOException("Process exited with " + result + "\nStandard Output:\n" + stdout + "\nError Output:\n" + stderr); + } + return junction; + } } diff --git a/core/src/test/java/jenkins/util/os/windows/WindowsCommandLineFormatterTest.java b/core/src/test/java/hudson/os/WindowsUtilTest.java similarity index 87% rename from core/src/test/java/jenkins/util/os/windows/WindowsCommandLineFormatterTest.java rename to core/src/test/java/hudson/os/WindowsUtilTest.java index d715fb2870..4fd9bb45d2 100644 --- a/core/src/test/java/jenkins/util/os/windows/WindowsCommandLineFormatterTest.java +++ b/core/src/test/java/hudson/os/WindowsUtilTest.java @@ -22,19 +22,21 @@ * THE SOFTWARE. */ -package jenkins.util.os.windows; +package hudson.os; import org.junit.Test; import static org.junit.Assert.*; -public class WindowsCommandLineFormatterTest { - +/** + * Test the test utility code, too! + */ +public class WindowsUtilTest { @Test public void testQuoteArgument() { String input = "C:\\Programs and \"Settings\"\\System32\\\\"; String expected = "\"C:\\Programs and \\\"Settings\\\"\\System32\\\\\\\\\""; - String actual = WindowsCommandLineFormatter.quoteArgument(input); + String actual = WindowsUtil.quoteArgument(input); assertEquals(expected, actual); } @@ -42,7 +44,7 @@ public class WindowsCommandLineFormatterTest { public void testQuoteArgumentForCmd() { String input = "hello \"\\world&"; String expected = "^\"hello \\^\"\\world^&^\""; - String actual = WindowsCommandLineFormatter.quoteArgumentForCmd(input); + String actual = WindowsUtil.quoteArgumentForCmd(input); assertEquals(expected, actual); } } \ No newline at end of file