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