subprocesses: customizable SubprocessFactory
Individual SubprocessBuilder instances can now use
a SubprocessFactory object other than the static
SubprocessBuilder.factory.
Also, WindowsSubprocessFactory is no longer a
singleton because it now stores state: whether to
use windows-style argument escaping or to use the
(broken) Bash-style escaping (causing https://github.com/bazelbuild/bazel/issues/7122).
These two changes allow:
- a safer way to mock out SubprocessFactory in
tests, because it's no longer necessary to
change global state (i.e. the static
SubprocessBuilder.factory member)
- testing old and new argument escaping semantics
in WindowsSubprocessTest
- adding a flag that switches between old and new
semantics in the SubprocessFactory, and thus
allows rolling out the bugfix with just a flag
flip
See https://github.com/bazelbuild/bazel/issues/7122
PiperOrigin-RevId: 234105692
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index e6b75db..0ceebc0 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -1048,7 +1048,7 @@
private static SubprocessFactory subprocessFactoryImplementation() {
if (!"0".equals(System.getProperty("io.bazel.EnableJni")) && OS.getCurrent() == OS.WINDOWS) {
- return WindowsSubprocessFactory.INSTANCE;
+ return new WindowsSubprocessFactory(false);
} else {
return JavaSubprocessFactory.INSTANCE;
}
@@ -1155,7 +1155,7 @@
"No module set the default hash function.", ExitCode.BLAZE_INTERNAL_ERROR, e);
}
Path.setFileSystemForSerialization(fs);
- SubprocessBuilder.setSubprocessFactory(subprocessFactoryImplementation());
+ SubprocessBuilder.setDefaultSubprocessFactory(subprocessFactoryImplementation());
Path outputUserRootPath = fs.getPath(outputUserRoot);
Path installBasePath = fs.getPath(installBase);
diff --git a/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java b/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
index d3ce1ee..e962e14 100644
--- a/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/shell/SubprocessBuilder.java
@@ -41,6 +41,7 @@
STREAM
}
+ private final SubprocessFactory factory;
private ImmutableList<String> argv;
private ImmutableMap<String, String> env;
private StreamAction stdoutAction;
@@ -51,15 +52,20 @@
private long timeoutMillis;
private boolean redirectErrorStream;
- static SubprocessFactory factory = JavaSubprocessFactory.INSTANCE;
+ static SubprocessFactory defaultFactory = JavaSubprocessFactory.INSTANCE;
- public static void setSubprocessFactory(SubprocessFactory factory) {
- SubprocessBuilder.factory = factory;
+ public static void setDefaultSubprocessFactory(SubprocessFactory factory) {
+ SubprocessBuilder.defaultFactory = factory;
}
public SubprocessBuilder() {
+ this(defaultFactory);
+ }
+
+ public SubprocessBuilder(SubprocessFactory factory) {
stdoutAction = StreamAction.STREAM;
stderrAction = StreamAction.STREAM;
+ this.factory = factory;
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
index 3ed4a6d..01e7588 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.windows;
+import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.Subprocess;
import com.google.devtools.build.lib.shell.SubprocessBuilder;
import com.google.devtools.build.lib.shell.SubprocessBuilder.StreamAction;
@@ -31,10 +32,10 @@
* A subprocess factory that uses the Win32 API.
*/
public class WindowsSubprocessFactory implements SubprocessFactory {
- public static final WindowsSubprocessFactory INSTANCE = new WindowsSubprocessFactory();
+ private final boolean windowsStyleArgEscaping;
- private WindowsSubprocessFactory() {
- // Singleton
+ public WindowsSubprocessFactory(boolean windowsStyleArgEscaping) {
+ this.windowsStyleArgEscaping = windowsStyleArgEscaping;
}
@Override
@@ -43,8 +44,7 @@
// DO NOT quote argv0, createProcess will do it for us.
String argv0 = processArgv0(argv.get(0));
- String argvRest =
- argv.size() > 1 ? WindowsProcesses.quoteCommandLine(argv.subList(1, argv.size())) : "";
+ String argvRest = argv.size() > 1 ? escapeArgvRest(argv.subList(1, argv.size())) : "";
byte[] env = convertEnvToNative(builder.getEnv());
String stdoutPath = getRedirectPath(builder.getStdout(), builder.getStdoutFile());
@@ -73,7 +73,25 @@
builder.getTimeoutMillis());
}
- public String processArgv0(String argv0) {
+ private String escapeArgvRest(List<String> argv) {
+ if (windowsStyleArgEscaping) {
+ StringBuilder result = new StringBuilder();
+ boolean first = true;
+ for (String arg : argv) {
+ if (first) {
+ first = false;
+ } else {
+ result.append(" ");
+ }
+ result.append(ShellUtils.windowsEscapeArg(arg));
+ }
+ return result.toString();
+ } else {
+ return WindowsProcesses.quoteCommandLine(argv);
+ }
+ }
+
+ public static String processArgv0(String argv0) {
// Normalize the path and make it Windows-style.
// If argv0 is at least MAX_PATH (260 chars) long, createNativeProcess calls GetShortPathNameW
// to obtain a 8dot3 name for it (thereby support long paths in CreateProcessA), but then argv0
@@ -83,10 +101,10 @@
// If it's not absolute, then it cannot be longer than MAX_PATH, since MAX_PATH also limits the
// length of file names.
PathFragment argv0fragment = PathFragment.create(argv0);
- return (argv0fragment.isAbsolute()) ? argv0fragment.getPathString().replace('/', '\\') : argv0;
+ return argv0fragment.isAbsolute() ? argv0fragment.getPathString().replace('/', '\\') : argv0;
}
- private String getRedirectPath(StreamAction action, File file) {
+ private static String getRedirectPath(StreamAction action, File file) {
switch (action) {
case DISCARD:
return "NUL"; // That's /dev/null on Windows
@@ -102,10 +120,8 @@
}
}
- /**
- * Converts an environment map to the format expected in lpEnvironment by CreateProcess().
- */
- private byte[] convertEnvToNative(Map<String, String> envMap) throws IOException {
+ /** Converts an environment map to the format expected in lpEnvironment by CreateProcess(). */
+ private static byte[] convertEnvToNative(Map<String, String> envMap) throws IOException {
Map<String, String> realEnv = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String, String> systemEnv = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (envMap != null) {
diff --git a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
index 45803d7..e37e13d 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
@@ -279,7 +279,7 @@
private FileSystem setupEnvironmentForFakeExecution() {
// Prevent any subprocess execution at all.
- SubprocessBuilder.setSubprocessFactory(new SubprocessInterceptor());
+ SubprocessBuilder.setDefaultSubprocessFactory(new SubprocessInterceptor());
resourceManager.setAvailableResources(
ResourceSet.create(/*memoryMb=*/1, /*cpuUsage=*/1, /*localTestCount=*/1));
return new InMemoryFileSystem();
@@ -292,7 +292,7 @@
*/
@Before
public final void setupEnvironmentForRealExecution() {
- SubprocessBuilder.setSubprocessFactory(JavaSubprocessFactory.INSTANCE);
+ SubprocessBuilder.setDefaultSubprocessFactory(JavaSubprocessFactory.INSTANCE);
resourceManager.setAvailableResources(LocalHostCapacity.getLocalHostCapacity());
}
@@ -308,7 +308,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
options.localSigkillGraceSeconds = 456;
@@ -364,7 +364,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
when(factory.create(any())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
options.localSigkillGraceSeconds = 456;
@@ -414,7 +414,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
options.localSigkillGraceSeconds = 456;
@@ -460,7 +460,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(3));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
LocalSpawnRunner runner =
@@ -508,7 +508,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenThrow(new IOException("I'm sorry, Dave"));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
LocalSpawnRunner runner =
@@ -595,7 +595,7 @@
}
}
});
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
LocalSpawnRunner runner =
@@ -627,7 +627,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
when(factory.create(any())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
LocalSpawnRunner runner =
@@ -654,7 +654,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
when(factory.create(any())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
LocalSpawnRunner runner =
@@ -684,7 +684,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
when(factory.create(any())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalEnvProvider localEnvProvider = mock(LocalEnvProvider.class);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
@@ -723,7 +723,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
options.localSigkillGraceSeconds = 654;
@@ -973,7 +973,7 @@
SubprocessFactory factory = mock(SubprocessFactory.class);
ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
- SubprocessBuilder.setSubprocessFactory(factory);
+ SubprocessBuilder.setDefaultSubprocessFactory(factory);
LocalSpawnRunner runner =
new TestedLocalSpawnRunner(
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsSubprocessTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsSubprocessTest.java
index 4fb0968..d392230 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsSubprocessTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsSubprocessTest.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.Subprocess;
import com.google.devtools.build.lib.shell.SubprocessBuilder;
import com.google.devtools.build.lib.testutil.TestSpec;
@@ -26,6 +27,7 @@
import com.google.devtools.build.lib.windows.jni.WindowsProcesses;
import com.google.devtools.build.runfiles.Runfiles;
import java.io.File;
+import java.util.function.Function;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -63,11 +65,10 @@
}
}
- @Test
- public void testSystemRootIsSetByDefault() throws Exception {
- SubprocessBuilder subprocessBuilder = new SubprocessBuilder();
+ private void assertSystemRootIsSetByDefault(boolean windowsStyleArgEscaping) throws Exception {
+ SubprocessBuilder subprocessBuilder =
+ new SubprocessBuilder(new WindowsSubprocessFactory(windowsStyleArgEscaping));
subprocessBuilder.setWorkingDirectory(new File("."));
- subprocessBuilder.setSubprocessFactory(WindowsSubprocessFactory.INSTANCE);
subprocessBuilder.setArgv(mockBinary, "-jar", mockSubprocess, "O$SYSTEMROOT");
process = subprocessBuilder.start();
process.waitFor();
@@ -79,10 +80,19 @@
}
@Test
- public void testSystemDriveIsSetByDefault() throws Exception {
- SubprocessBuilder subprocessBuilder = new SubprocessBuilder();
+ public void testSystemRootIsSetByDefaultNoWindowsStyleArgEscaping() throws Exception {
+ assertSystemRootIsSetByDefault(false);
+ }
+
+ @Test
+ public void testSystemRootIsSetByDefaultWithWindowsStyleArgEscaping() throws Exception {
+ assertSystemRootIsSetByDefault(true);
+ }
+
+ private void assertSystemDriveIsSetByDefault(boolean windowsStyleArgEscaping) throws Exception {
+ SubprocessBuilder subprocessBuilder =
+ new SubprocessBuilder(new WindowsSubprocessFactory(windowsStyleArgEscaping));
subprocessBuilder.setWorkingDirectory(new File("."));
- subprocessBuilder.setSubprocessFactory(WindowsSubprocessFactory.INSTANCE);
subprocessBuilder.setArgv(mockBinary, "-jar", mockSubprocess, "O$SYSTEMDRIVE");
process = subprocessBuilder.start();
process.waitFor();
@@ -94,10 +104,19 @@
}
@Test
- public void testSystemRootIsSet() throws Exception {
- SubprocessBuilder subprocessBuilder = new SubprocessBuilder();
+ public void testSystemDriveIsSetByDefaultNoWindowsStyleArgEscaping() throws Exception {
+ assertSystemDriveIsSetByDefault(false);
+ }
+
+ @Test
+ public void testSystemDriveIsSetByDefaultWithWindowsStyleArgEscaping() throws Exception {
+ assertSystemDriveIsSetByDefault(true);
+ }
+
+ private void assertSystemRootIsSet(boolean windowsStyleArgEscaping) throws Exception {
+ SubprocessBuilder subprocessBuilder =
+ new SubprocessBuilder(new WindowsSubprocessFactory(windowsStyleArgEscaping));
subprocessBuilder.setWorkingDirectory(new File("."));
- subprocessBuilder.setSubprocessFactory(WindowsSubprocessFactory.INSTANCE);
subprocessBuilder.setArgv(mockBinary, "-jar", mockSubprocess, "O$SYSTEMROOT");
// Case shouldn't matter on Windows
subprocessBuilder.setEnv(ImmutableMap.of("SystemRoot", "C:\\MySystemRoot"));
@@ -111,10 +130,19 @@
}
@Test
- public void testSystemDriveIsSet() throws Exception {
- SubprocessBuilder subprocessBuilder = new SubprocessBuilder();
+ public void testSystemRootIsSetNoWindowsStyleArgEscaping() throws Exception {
+ assertSystemRootIsSet(false);
+ }
+
+ @Test
+ public void testSystemRootIsSetWithWindowsStyleArgEscaping() throws Exception {
+ assertSystemRootIsSet(true);
+ }
+
+ private void assertSystemDriveIsSet(boolean windowsStyleArgEscaping) throws Exception {
+ SubprocessBuilder subprocessBuilder =
+ new SubprocessBuilder(new WindowsSubprocessFactory(windowsStyleArgEscaping));
subprocessBuilder.setWorkingDirectory(new File("."));
- subprocessBuilder.setSubprocessFactory(WindowsSubprocessFactory.INSTANCE);
subprocessBuilder.setArgv(mockBinary, "-jar", mockSubprocess, "O$SYSTEMDRIVE");
// Case shouldn't matter on Windows
subprocessBuilder.setEnv(ImmutableMap.of("SystemDrive", "X:"));
@@ -127,6 +155,16 @@
assertThat(new String(buf, UTF_8).trim()).isEqualTo("X:");
}
+ @Test
+ public void testSystemDriveIsSetNoWindowsStyleArgEscaping() throws Exception {
+ assertSystemDriveIsSet(false);
+ }
+
+ @Test
+ public void testSystemDriveIsSetWithWindowsStyleArgEscaping() throws Exception {
+ assertSystemDriveIsSet(true);
+ }
+
/**
* An argument and its command-line-escaped counterpart.
*
@@ -143,7 +181,9 @@
};
/** Asserts that a subprocess correctly receives command line arguments. */
- private void assertSubprocessReceivesArgsAsIntended(ArgPair... args) throws Exception {
+ private void assertSubprocessReceivesArgsAsIntended(
+ boolean windowsStyleArgEscaping, Function<String, String> escaper, ArgPair... args)
+ throws Exception {
// Look up the path of the printarg.exe utility.
String printArgExe =
runfiles.rlocation("io_bazel/src/test/java/com/google/devtools/build/lib/printarg.exe");
@@ -151,13 +191,12 @@
for (ArgPair arg : args) {
// Assert that the command-line encoding logic works as intended.
- assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of(arg.original)))
- .isEqualTo(arg.escaped);
+ assertThat(escaper.apply(arg.original)).isEqualTo(arg.escaped);
// Create a separate subprocess just for this argument.
- SubprocessBuilder subprocessBuilder = new SubprocessBuilder();
+ SubprocessBuilder subprocessBuilder =
+ new SubprocessBuilder(new WindowsSubprocessFactory(windowsStyleArgEscaping));
subprocessBuilder.setWorkingDirectory(new File("."));
- subprocessBuilder.setSubprocessFactory(WindowsSubprocessFactory.INSTANCE);
subprocessBuilder.setArgv(printArgExe, arg.original);
process = subprocessBuilder.start();
process.waitFor();
@@ -173,8 +212,10 @@
}
@Test
- public void testSubprocessReceivesArgsAsIntended() throws Exception {
+ public void testSubprocessReceivesArgsAsIntendedNoWindowsStyleArgEscaping() throws Exception {
assertSubprocessReceivesArgsAsIntended(
+ false,
+ x -> WindowsProcesses.quoteCommandLine(ImmutableList.of(x)),
new ArgPair("", "\"\""),
new ArgPair(" ", "\" \""),
new ArgPair("foo", "foo"),
@@ -184,4 +225,54 @@
// it fails to properly escape things like a single backslash followed by a quote, e.g. a\"b
// Fix the escaping logic and add more test here.
}
+
+ @Test
+ public void testSubprocessReceivesArgsAsIntendedWithWindowsStyleArgEscaping() throws Exception {
+ assertSubprocessReceivesArgsAsIntended(
+ true,
+ x -> ShellUtils.windowsEscapeArg(x),
+ new ArgPair("", "\"\""),
+ new ArgPair(" ", "\" \""),
+ new ArgPair("\"", "\"\\\"\""),
+ new ArgPair("\"\\", "\"\\\"\\\\\""),
+ new ArgPair("\\", "\\"),
+ new ArgPair("\\\"", "\"\\\\\\\"\""),
+ new ArgPair("with space", "\"with space\""),
+ new ArgPair("with^caret", "with^caret"),
+ new ArgPair("space ^caret", "\"space ^caret\""),
+ new ArgPair("caret^ space", "\"caret^ space\""),
+ new ArgPair("with\"quote", "\"with\\\"quote\""),
+ new ArgPair("with\\backslash", "with\\backslash"),
+ new ArgPair("one\\ backslash and \\space", "\"one\\ backslash and \\space\""),
+ new ArgPair("two\\\\backslashes", "two\\\\backslashes"),
+ new ArgPair("two\\\\ backslashes \\\\and space", "\"two\\\\ backslashes \\\\and space\""),
+ new ArgPair("one\\\"x", "\"one\\\\\\\"x\""),
+ new ArgPair("two\\\\\"x", "\"two\\\\\\\\\\\"x\""),
+ new ArgPair("a \\ b", "\"a \\ b\""),
+ new ArgPair("a \\\" b", "\"a \\\\\\\" b\""),
+ new ArgPair("A", "A"),
+ new ArgPair("\"a\"", "\"\\\"a\\\"\""),
+ new ArgPair("B C", "\"B C\""),
+ new ArgPair("\"b c\"", "\"\\\"b c\\\"\""),
+ new ArgPair("D\"E", "\"D\\\"E\""),
+ new ArgPair("\"d\"e\"", "\"\\\"d\\\"e\\\"\""),
+ new ArgPair("C:\\F G", "\"C:\\F G\""),
+ new ArgPair("\"C:\\f g\"", "\"\\\"C:\\f g\\\"\""),
+ new ArgPair("C:\\H\"I", "\"C:\\H\\\"I\""),
+ new ArgPair("\"C:\\h\"i\"", "\"\\\"C:\\h\\\"i\\\"\""),
+ new ArgPair("C:\\J\\\"K", "\"C:\\J\\\\\\\"K\""),
+ new ArgPair("\"C:\\j\\\"k\"", "\"\\\"C:\\j\\\\\\\"k\\\"\""),
+ new ArgPair("C:\\L M ", "\"C:\\L M \""),
+ new ArgPair("\"C:\\l m \"", "\"\\\"C:\\l m \\\"\""),
+ new ArgPair("C:\\N O\\", "\"C:\\N O\\\\\""),
+ new ArgPair("\"C:\\n o\\\"", "\"\\\"C:\\n o\\\\\\\"\""),
+ new ArgPair("C:\\P Q\\ ", "\"C:\\P Q\\ \""),
+ new ArgPair("\"C:\\p q\\ \"", "\"\\\"C:\\p q\\ \\\"\""),
+ new ArgPair("C:\\R\\S\\", "C:\\R\\S\\"),
+ new ArgPair("C:\\R x\\S\\", "\"C:\\R x\\S\\\\\""),
+ new ArgPair("\"C:\\r\\s\\\"", "\"\\\"C:\\r\\s\\\\\\\"\""),
+ new ArgPair("\"C:\\r x\\s\\\"", "\"\\\"C:\\r x\\s\\\\\\\"\""),
+ new ArgPair("C:\\T U\\W\\", "\"C:\\T U\\W\\\\\""),
+ new ArgPair("\"C:\\t u\\w\\\"", "\"\\\"C:\\t u\\w\\\\\\\"\""));
+ }
}