[PATCH 2/4] [cmd] Add full for /R support

Ann and Jason Edmeades jason at edmeades.me.uk
Mon Oct 1 06:38:56 CDT 2012


This adds support for directory recursion using for /r. Whilst the delta
looks ugly, the majority of it is indenting the main loop within 'for'
with an extra outside while loop which tracks what directories are still
to enumerate.

Note: Tests are written in this odd way due to findfirst/next ordering
being different between windows and wine. I experimented with wrappering
unix sort with a windows shell, but it still gave different sort ordering
due to collation ordering differences (Unix maps lower to upper case when
ignoring case, windows does the opposite) so I worked around the problem.
Note2: One test skipped on NT4 as its just plain broken with respect to
for /r and stem root being supplied.

[Fixes bug 28311]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20121001/43dbb51c/attachment-0001.html>
-------------- next part --------------
From 86168d94fa9097c4f22d39fde9e1584816ce3de1 Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Thu, 27 Sep 2012 19:58:56 +0100
Subject: [PATCH 2/4] [cmd] Add full for /R support

This adds support for directory recursion using for /r. Whilst the delta
looks ugly, the majority of it is indenting the main loop within 'for'
with an extra outside while loop which tracks what directories are still
to enumerate.

Note: Tests are written in this odd way due to findfirst/next ordering
being different between windows and wine. I experimented with wrappering
unix sort with a windows shell, but it still gave different sort ordering
due to collation ordering differences (Unix maps lower to upper case when
ignoring case, windows does the opposite) so I worked around the problem.
Note2: One test skipped on NT4 as its just plain broken with respect to
for /r and stem root being supplied.

[Fixes bug 28311]
---
 programs/cmd/builtins.c                  |  428 ++++++++++++++++++------------
 programs/cmd/tests/test_builtins.cmd     |  123 +++++++++
 programs/cmd/tests/test_builtins.cmd.exp |   10 +
 3 files changed, 395 insertions(+), 166 deletions(-)

diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index e54c444..337502b 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1079,10 +1079,13 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
   WCHAR variable[4];
   WCHAR *firstCmd;
   int thisDepth;
+  WCHAR optionsRoot[MAXSTRING] = {'\0'};
+  DIRECTORY_STACK *dirsToWalk = NULL;
 
   BOOL   expandDirs  = FALSE;
   BOOL   useNumbers  = FALSE;
   BOOL   doFileset   = FALSE;
+  BOOL   doRecurse   = FALSE;
   BOOL   doExecuted  = FALSE;  /* Has the 'do' part been executed */
   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
   int    itemNum;
@@ -1103,20 +1106,23 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
       case 'R':
       case 'F':
           {
-              BOOL isRecursive = (*thisArg == 'R');
+              /* When recursing directories, use current directory as the starting point unless
+                 subsequently overridden */
+              doRecurse = (toupperW(*thisArg) == 'R');
+              if (doRecurse) GetCurrentDirectoryW(MAXSTRING, optionsRoot);
 
-              if (!isRecursive)
-                  doFileset = TRUE;
+              doFileset = (toupperW(*thisArg) == 'F');
 
-              /* Retrieve next parameter to see if is path/options */
-              thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, !isRecursive);
+              /* Retrieve next parameter to see if is root/options (raw form required
+                 with for /f, or unquoted in for /r)                                  */
+              thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
 
               /* Next parm is either qualifier, path/options or variable -
                  only care about it if it is the path/options              */
               if (thisArg && *thisArg != '/' && *thisArg != '%') {
                   parameterNo++;
-                  if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
-                  else {
+                  strcpyW(optionsRoot, thisArg);
+                  if (!doRecurse) {
                       static unsigned int once;
                       if (!once++) WINE_FIXME("/F needs to handle options\n");
                   }
@@ -1137,6 +1143,17 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
       return;
   }
 
+  /* Set up the list of directories to recurse if we are going to */
+  if (doRecurse) {
+       /* Allocate memory, add to list */
+       dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
+       dirsToWalk->next = NULL;
+       dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
+                                       (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
+       strcpyW(dirsToWalk->dirName, optionsRoot);
+       WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
+  }
+
   /* Variable should follow */
   strcpyW(variable, thisArg);
   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
@@ -1178,198 +1195,277 @@ void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
       return;
   }
 
-  /* Save away the starting position for the commands (and offset for the
-     first one                                                           */
-  cmdStart = *cmdList;
   cmdEnd   = *cmdList;
-  firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
-  itemNum  = 0;
-
-  thisSet = setStart;
-  /* Loop through all set entries */
-  while (thisSet &&
-         thisSet->command != NULL &&
-         thisSet->bracketDepth >= thisDepth) {
-
-    /* Loop through all entries on the same line */
-    WCHAR *item;
-    WCHAR *itemStart;
-
-    WINE_TRACE("Processing for set %p\n", thisSet);
-    i = 0;
-    while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
-
-      /*
-       * If the parameter within the set has a wildcard then search for matching files
-       * otherwise do a literal substitution.
-       */
-      static const WCHAR wildcards[] = {'*','?','\0'};
-      thisCmdStart = cmdStart;
-
-      itemNum++;
-      WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
-
-      if (!useNumbers && !doFileset) {
-          if (strpbrkW (item, wildcards)) {
-            hff = FindFirstFileW(item, &fd);
-            if (hff != INVALID_HANDLE_VALUE) {
-              do {
-                BOOL isDirectory = FALSE;
-
-                if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
-
-                /* Handle as files or dirs appropriately, but ignore . and .. */
-                if (isDirectory == expandDirs &&
-                    (strcmpW(fd.cFileName, dotdotW) != 0) &&
-                    (strcmpW(fd.cFileName, dotW) != 0))
-                {
-                  thisCmdStart = cmdStart;
-                  WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
-                  doExecuted = TRUE;
-                  WCMD_part_execute (&thisCmdStart, firstCmd, variable,
-                                               fd.cFileName, FALSE, TRUE);
-                }
 
-              } while (FindNextFileW(hff, &fd) != 0);
-              FindClose (hff);
-            }
-          } else {
-            doExecuted = TRUE;
-            WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
+  /* Loop repeatedly per-directory we are potentially walking, when in for /r
+     mode, or once for the rest of the time.                                  */
+  do {
+    WCHAR fullitem[MAXSTRING];
+    static const WCHAR slashstarW[] = {'\\','*','\0'};
+
+    /* Save away the starting position for the commands (and offset for the
+       first one)                                                           */
+    cmdStart = *cmdList;
+    firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
+    itemNum  = 0;
+
+    /* If we are recursing directories (ie /R), add all sub directories now, then
+       prefix the root when searching for the item */
+    if (dirsToWalk) {
+      DIRECTORY_STACK *remainingDirs = dirsToWalk;
+
+      /* Build a generic search and add all directories on the list of directories
+         still to walk                                                             */
+      strcpyW(fullitem, dirsToWalk->dirName);
+      strcatW(fullitem, slashstarW);
+      hff = FindFirstFileW(fullitem, &fd);
+      if (hff != INVALID_HANDLE_VALUE) {
+        do {
+          WINE_TRACE("Looking for subdirectories\n");
+          if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+              (strcmpW(fd.cFileName, dotdotW) != 0) &&
+              (strcmpW(fd.cFileName, dotW) != 0))
+          {
+            /* Allocate memory, add to list */
+            DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
+            WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
+            toWalk->next = remainingDirs->next;
+            remainingDirs->next = toWalk;
+            remainingDirs = toWalk;
+            toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
+                                        sizeof(WCHAR) *
+                                        (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
+            strcpyW(toWalk->dirName, dirsToWalk->dirName);
+            strcatW(toWalk->dirName, slashW);
+            strcatW(toWalk->dirName, fd.cFileName);
+            WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
+                       toWalk, toWalk->next);
           }
+        } while (FindNextFileW(hff, &fd) != 0);
+        WINE_TRACE("Finished adding all subdirectories\n");
+        FindClose (hff);
+      }
+    }
 
-      } else if (useNumbers) {
-          /* Convert the first 3 numbers to signed longs and save */
-          if (itemNum <=3) numbers[itemNum-1] = atolW(item);
-          /* else ignore them! */
+    thisSet = setStart;
+    /* Loop through all set entries */
+    while (thisSet &&
+           thisSet->command != NULL &&
+           thisSet->bracketDepth >= thisDepth) {
 
-      /* Filesets - either a list of files, or a command to run and parse the output */
-      } else if (doFileset && *itemStart != '"') {
+      /* Loop through all entries on the same line */
+      WCHAR *item;
+      WCHAR *itemStart;
 
-          HANDLE input;
-          WCHAR temp_file[MAX_PATH];
+      WINE_TRACE("Processing for set %p\n", thisSet);
+      i = 0;
+      while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
 
-          WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
-                     wine_dbgstr_w(item));
+        /*
+         * If the parameter within the set has a wildcard then search for matching files
+         * otherwise do a literal substitution.
+         */
+        static const WCHAR wildcards[] = {'*','?','\0'};
+        thisCmdStart = cmdStart;
 
-          /* If backquote or single quote, we need to launch that command
-             and parse the results - use a temporary file                 */
-          if (*itemStart == '`' || *itemStart == '\'') {
+        itemNum++;
+        WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
 
-              WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
-              static const WCHAR redirOut[] = {'>','%','s','\0'};
-              static const WCHAR cmdW[]     = {'C','M','D','\0'};
+        if (!useNumbers && !doFileset) {
+            WCHAR fullitem[MAXSTRING];
 
-              /* Remove trailing character */
-              itemStart[strlenW(itemStart)-1] = 0x00;
+            /* Now build the item to use / search for in the specified directory,
+               as it is fully qualified in the /R case */
+            if (dirsToWalk) {
+              strcpyW(fullitem, dirsToWalk->dirName);
+              strcatW(fullitem, slashW);
+              strcatW(fullitem, item);
+            } else {
+              strcpyW(fullitem, item);
+            }
 
-              /* Get temp filename */
-              GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
-              GetTempFileNameW(temp_path, cmdW, 0, temp_file);
+            if (strpbrkW (fullitem, wildcards)) {
+
+              hff = FindFirstFileW(fullitem, &fd);
+              if (hff != INVALID_HANDLE_VALUE) {
+                do {
+                  BOOL isDirectory = FALSE;
+
+                  if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
+
+                  /* Handle as files or dirs appropriately, but ignore . and .. */
+                  if (isDirectory == expandDirs &&
+                      (strcmpW(fd.cFileName, dotdotW) != 0) &&
+                      (strcmpW(fd.cFileName, dotW) != 0))
+                  {
+                      thisCmdStart = cmdStart;
+                      WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
+
+                      if (doRecurse) {
+                          strcpyW(fullitem, dirsToWalk->dirName);
+                          strcatW(fullitem, slashW);
+                          strcatW(fullitem, fd.cFileName);
+                      } else {
+                          strcpyW(fullitem, fd.cFileName);
+                      }
+                      doExecuted = TRUE;
+                      WCMD_part_execute (&thisCmdStart, firstCmd, variable,
+                                                   fullitem, FALSE, TRUE);
+                      cmdEnd = thisCmdStart;
+                  }
+                } while (FindNextFileW(hff, &fd) != 0);
+                FindClose (hff);
+              }
+            } else {
+              doExecuted = TRUE;
+              WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
+              cmdEnd = thisCmdStart;
+            }
 
-              /* Execute program and redirect output */
-              wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
-              WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
+        } else if (useNumbers) {
+            /* Convert the first 3 numbers to signed longs and save */
+            if (itemNum <=3) numbers[itemNum-1] = atolW(item);
+            /* else ignore them! */
 
-              /* Open the file, read line by line and process */
-              input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
-                                  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
-          } else {
+        /* Filesets - either a list of files, or a command to run and parse the output */
+        } else if (doFileset && *itemStart != '"') {
 
-              /* Open the file, read line by line and process */
-              input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
-                                  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
-          }
+            HANDLE input;
+            WCHAR temp_file[MAX_PATH];
 
-          /* Process the input file */
-          if (input == INVALID_HANDLE_VALUE) {
-            WCMD_print_error ();
-            WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
-            errorlevel = 1;
-            return; /* FOR loop aborts at first failure here */
+            WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
+                       wine_dbgstr_w(item));
 
-          } else {
+            /* If backquote or single quote, we need to launch that command
+               and parse the results - use a temporary file                 */
+            if (*itemStart == '`' || *itemStart == '\'') {
 
-            WCHAR buffer[MAXSTRING] = {'\0'};
-            WCHAR *where, *parm;
+                WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
+                static const WCHAR redirOut[] = {'>','%','s','\0'};
+                static const WCHAR cmdW[]     = {'C','M','D','\0'};
 
-            while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
+                /* Remove trailing character */
+                itemStart[strlenW(itemStart)-1] = 0x00;
 
-              /* Skip blank lines*/
-              parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
-              WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
-                         wine_dbgstr_w(buffer));
+                /* Get temp filename */
+                GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
+                GetTempFileNameW(temp_path, cmdW, 0, temp_file);
 
-              if (where) {
-                  /* FIXME: The following should be moved into its own routine and
-                     reused for the string literal parsing below                  */
-                  thisCmdStart = cmdStart;
-                  doExecuted = TRUE;
-                  WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
-                  cmdEnd = thisCmdStart;
-              }
+                /* Execute program and redirect output */
+                wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
+                WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
 
-              buffer[0] = 0x00;
+                /* Open the file, read line by line and process */
+                input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
+                                    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+            } else {
 
+                /* Open the file, read line by line and process */
+                input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
+                                    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
             }
-            CloseHandle (input);
-          }
 
-          /* Delete the temporary file */
-          if (*itemStart == '`' || *itemStart == '\'') {
-              DeleteFileW(temp_file);
-          }
+            /* Process the input file */
+            if (input == INVALID_HANDLE_VALUE) {
+              WCMD_print_error ();
+              WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
+              errorlevel = 1;
+              return; /* FOR loop aborts at first failure here */
 
-      /* Filesets - A string literal */
-      } else if (doFileset && *itemStart == '"') {
-          WCHAR buffer[MAXSTRING] = {'\0'};
-          WCHAR *where, *parm;
-
-          /* Skip blank lines, and re-extract parameter now string has quotes removed */
-          strcpyW(buffer, item);
-          parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
-          WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
-                       wine_dbgstr_w(buffer));
-
-          if (where) {
-              /* FIXME: The following should be moved into its own routine and
-                 reused for the string literal parsing below                  */
-              thisCmdStart = cmdStart;
-              doExecuted = TRUE;
-              WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
-              cmdEnd = thisCmdStart;
-          }
+            } else {
+
+              WCHAR buffer[MAXSTRING] = {'\0'};
+              WCHAR *where, *parm;
+
+              while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
+
+                /* Skip blank lines*/
+                parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
+                WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
+                           wine_dbgstr_w(buffer));
+
+                if (where) {
+                    /* FIXME: The following should be moved into its own routine and
+                       reused for the string literal parsing below                  */
+                    thisCmdStart = cmdStart;
+                    doExecuted = TRUE;
+                    WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
+                    cmdEnd = thisCmdStart;
+                }
+
+                buffer[0] = 0x00;
+
+              }
+              CloseHandle (input);
+            }
+
+            /* Delete the temporary file */
+            if (*itemStart == '`' || *itemStart == '\'') {
+                DeleteFileW(temp_file);
+            }
+
+        /* Filesets - A string literal */
+        } else if (doFileset && *itemStart == '"') {
+            WCHAR buffer[MAXSTRING] = {'\0'};
+            WCHAR *where, *parm;
+
+            /* Skip blank lines, and re-extract parameter now string has quotes removed */
+            strcpyW(buffer, item);
+            parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
+            WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
+                         wine_dbgstr_w(buffer));
+
+            if (where) {
+                /* FIXME: The following should be moved into its own routine and
+                   reused for the string literal parsing below                  */
+                thisCmdStart = cmdStart;
+                doExecuted = TRUE;
+                WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
+                cmdEnd = thisCmdStart;
+            }
+        }
+
+        WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
+        i++;
       }
 
-      WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
-      cmdEnd = thisCmdStart;
-      i++;
+      /* Move onto the next set line */
+      thisSet = thisSet->nextcommand;
     }
 
-    /* Move onto the next set line */
-    thisSet = thisSet->nextcommand;
-  }
+    /* If /L is provided, now run the for loop */
+    if (useNumbers) {
+        WCHAR thisNum[20];
+        static const WCHAR fmt[] = {'%','d','\0'};
+
+        WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
+                   numbers[0], numbers[2], numbers[1]);
+        for (i=numbers[0];
+             (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
+             i=i + numbers[1]) {
 
-  /* If /L is provided, now run the for loop */
-  if (useNumbers) {
-      WCHAR thisNum[20];
-      static const WCHAR fmt[] = {'%','d','\0'};
+            sprintfW(thisNum, fmt, i);
+            WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
 
-      WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
-                 numbers[0], numbers[2], numbers[1]);
-      for (i=numbers[0];
-           (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
-           i=i + numbers[1]) {
+            thisCmdStart = cmdStart;
+            doExecuted = TRUE;
+            WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
+        }
+        cmdEnd = thisCmdStart;
+    }
 
-          sprintfW(thisNum, fmt, i);
-          WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
+    /* If we are walking directories, move on to any which remain */
+    if (dirsToWalk != NULL) {
+      DIRECTORY_STACK *nextDir = dirsToWalk->next;
+      HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
+      HeapFree(GetProcessHeap(), 0, dirsToWalk);
+      dirsToWalk = nextDir;
+      if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
+                                 wine_dbgstr_w(dirsToWalk->dirName));
+      else WINE_TRACE("Finished all directories.\n");
+    }
 
-          thisCmdStart = cmdStart;
-          doExecuted = TRUE;
-          WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
-      }
-      cmdEnd = thisCmdStart;
-  }
+  } while (dirsToWalk != NULL);
 
   /* Now skip over the do part if we did not perform the for loop so far.
      We store in cmdEnd the next command after the do block, but we only
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index d3bdf04..f564aed 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -726,6 +726,129 @@ rem del tmp
 rem for /d %%i in (*) do echo %%i>> tmp
 rem sort < tmp
 rem del tmp
+echo > baz\bazbaz
+goto :TestForR
+
+:SetExpected
+del temp.bat 2>nul
+call :WriteLine set found=N
+for /l %%i in (1,1,%expectedresults%) do (
+  call :WriteLine if "%%%%expectedresults.%%i%%%%"=="%%%%1" set found=Y
+  call :WriteLine if "%%%%found%%%%"=="Y" set expectedresults.%%i=
+  call :WriteLine if "%%%%found%%%%"=="Y" goto :eof
+)
+call :WriteLine echo Got unexpected result: "%%%%1"
+goto :eof
+
+:WriteLine
+echo %*>> temp.bat
+goto :EOF
+
+:ValidateExpected
+del temp.bat 2>nul
+for /l %%i in (1,1,%expectedresults%) do  call :WriteLine if not "%%%%expectedresults.%%i%%%%"=="" echo Found missing result: "%%%%expectedresults.%%i%%%%"
+call temp.bat
+del temp.bat 2>nul
+goto :eof
+
+:TestForR
+rem %CD% does not tork on NT4 so use the following workaround
+for /d %%i in (.) do set CURDIR=%%~dpnxi
+
+echo --- for /R
+echo Plain directory enumeration
+set expectedresults=4
+set expectedresults.1=%CURDIR%\.
+set expectedresults.2=%CURDIR%\bar\.
+set expectedresults.3=%CURDIR%\baz\.
+set expectedresults.4=%CURDIR%\foo\.
+call :SetExpected
+for /R %%i in (.) do call temp.bat %%i
+call :ValidateExpected
+
+echo Plain directory enumeration from provided root
+set expectedresults=4
+set expectedresults.1=%CURDIR%\.
+set expectedresults.2=%CURDIR%\bar\.
+set expectedresults.3=%CURDIR%\baz\.
+set expectedresults.4=%CURDIR%\foo\.
+if "%CD%"=="" goto :SkipBrokenNT4
+call :SetExpected
+for /R "%CURDIR%" %%i in (.) do call temp.bat %%i
+call :ValidateExpected
+:SkipBrokenNT4
+
+echo File enumeration
+set expectedresults=2
+set expectedresults.1=%CURDIR%\baz\bazbaz
+set expectedresults.2=%CURDIR%\bazbaz
+call :SetExpected
+for /R %%i in (baz*) do call temp.bat %%i
+call :ValidateExpected
+
+echo File enumeration from provided root
+set expectedresults=2
+set expectedresults.1=%CURDIR%\baz\bazbaz
+set expectedresults.2=%CURDIR%\bazbaz
+call :SetExpected
+for /R %%i in (baz*) do call temp.bat %%i
+call :ValidateExpected
+
+echo Mixed enumeration
+set expectedresults=6
+set expectedresults.1=%CURDIR%\.
+set expectedresults.2=%CURDIR%\bar\.
+set expectedresults.3=%CURDIR%\baz\.
+set expectedresults.4=%CURDIR%\baz\bazbaz
+set expectedresults.5=%CURDIR%\bazbaz
+set expectedresults.6=%CURDIR%\foo\.
+call :SetExpected
+for /R %%i in (. baz*) do call temp.bat %%i
+call :ValidateExpected
+
+echo Mixed enumeration from provided root
+set expectedresults=6
+set expectedresults.1=%CURDIR%\.
+set expectedresults.2=%CURDIR%\bar\.
+set expectedresults.3=%CURDIR%\baz\.
+set expectedresults.4=%CURDIR%\baz\bazbaz
+set expectedresults.5=%CURDIR%\bazbaz
+set expectedresults.6=%CURDIR%\foo\.
+call :SetExpected
+for /R %%i in (. baz*) do call temp.bat %%i
+call :ValidateExpected
+
+echo With duplicates enumeration
+set expectedresults=12
+set expectedresults.1=%CURDIR%\bar\bazbaz
+set expectedresults.2=%CURDIR%\bar\fred
+set expectedresults.3=%CURDIR%\baz\bazbaz
+set expectedresults.4=%CURDIR%\baz\bazbaz
+set expectedresults.5=%CURDIR%\baz\bazbaz
+set expectedresults.6=%CURDIR%\baz\fred
+set expectedresults.7=%CURDIR%\bazbaz
+set expectedresults.8=%CURDIR%\bazbaz
+set expectedresults.9=%CURDIR%\bazbaz
+set expectedresults.10=%CURDIR%\foo\bazbaz
+set expectedresults.11=%CURDIR%\foo\fred
+set expectedresults.12=%CURDIR%\fred
+call :SetExpected
+for /R %%i in (baz* bazbaz fred ba*) do call temp.bat %%i
+call :ValidateExpected
+
+echo Strip missing wildcards, keep unwildcarded names
+set expectedresults=6
+set expectedresults.1=%CURDIR%\bar\jim
+set expectedresults.2=%CURDIR%\baz\bazbaz
+set expectedresults.3=%CURDIR%\baz\jim
+set expectedresults.4=%CURDIR%\bazbaz
+set expectedresults.5=%CURDIR%\foo\jim
+set expectedresults.6=%CURDIR%\jim
+call :SetExpected
+for /R %%i in (baz* fred* jim) do call temp.bat %%i
+call :ValidateExpected
+
+echo for /R passed
 cd .. & rd /s/Q foobar
 echo --- for /L
 rem Some cases loop forever writing 0s, like e.g. (1,0,1), (1,a,3) or (a,b,c); those can't be tested here
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index 21440b5..435d0e6 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -495,6 +495,16 @@ bar at space@
 PASSED
 xxx - Should be xxx
 Expected second line
+--- for /R
+Plain directory enumeration
+Plain directory enumeration from provided root
+File enumeration
+File enumeration from provided root
+Mixed enumeration
+Mixed enumeration from provided root
+With duplicates enumeration
+Strip missing wildcards, keep unwildcarded names
+for /R passed
 --- for /L
 1
 3
-- 
1.7.9.5


More information about the wine-patches mailing list