This is part XXV of command engine rewrite.
This MR tackles three items: - fixing some corner cases in variable expansion. Note, there's still todo_wine in the non regression tests, but I don't feel like fixing them for now (like an un-paired ! within double quotes when delayed expansion is enabled - which behaves differently then not within double quotes) - breaking out of FOR loops with 'EXIT /B'. I'm damn sure I dealt with this earlier on, it likely fails into the cracks during some rebase. - somme builtin commands behave differently when run from .BAT or .CMD files.
From: Eric Pouech epouech@codeweavers.com
Simplifying a bit the code, removing extraneous data copy...
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/wcmdmain.c | 183 ++++++++++++---------------------------- 1 file changed, 52 insertions(+), 131 deletions(-)
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 441340dd374..4c30094cc76 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -514,158 +514,79 @@ WCHAR *WCMD_strip_quotes(WCHAR *cmd) { return lastquote; }
- -/************************************************************************* - * WCMD_is_magic_envvar - * Return TRUE if s is '%'magicvar'%' - * and is not masked by a real environment variable. - */ - -static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar) -{ - int len; - - if (s[0] != '%') - return FALSE; /* Didn't begin with % */ - len = lstrlenW(s); - if (len < 2 || s[len-1] != '%') - return FALSE; /* Didn't end with another % */ - - if (CompareStringW(LOCALE_USER_DEFAULT, - NORM_IGNORECASE | SORT_STRINGSORT, - s+1, len-2, magicvar, -1) != CSTR_EQUAL) { - /* Name doesn't match. */ - return FALSE; - } - - if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) { - /* Masked by real environment variable. */ - return FALSE; - } - - return TRUE; -} - /************************************************************************* * WCMD_expand_envvar * * Expands environment variables, allowing for WCHARacter substitution */ -static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar) +static WCHAR *WCMD_expand_envvar(WCHAR *start) { WCHAR *endOfVar = NULL, *s; WCHAR *colonpos = NULL; WCHAR thisVar[MAXSTRING]; WCHAR thisVarContents[MAXSTRING]; - WCHAR savedchar = 0x00; int len; - WCHAR Delims[] = L"%:"; /* First char gets replaced appropriately */ - - WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar); - - /* Find the end of the environment variable, and extract name */ - Delims[0] = startchar; - endOfVar = wcspbrk(start+1, Delims); - - if (endOfVar == NULL || *endOfVar==' ') {
- /* In batch program, missing terminator for % and no following - ':' just removes the '%' */ - if (context) { - return WCMD_strsubstW(start, start + 1, NULL, 0); - } else { - - /* In command processing, just ignore it - allows command line - syntax like: for %i in (a.a) do echo %i */ - return start+1; - } - } + WINE_TRACE("Expanding: %s\n", wine_dbgstr_w(start));
- /* If ':' found, process remaining up until '%' (or stop at ':' if - a missing '%' */ - if (*endOfVar==':') { - WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar); - if (endOfVar2 != NULL) endOfVar = endOfVar2; - } + endOfVar = wcschr(start + 1, *start); + if (!endOfVar) + /* no corresponding closing char... either skip startchar in batch, or leave untouched otherwise */ + return context ? WCMD_strsubstW(start, start + 1, NULL, 0) : start + 1;
- memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR)); - thisVar[(endOfVar - start)+1] = 0x00; - colonpos = wcschr(thisVar+1, ':'); + memcpy(thisVar, start + 1, (endOfVar - start - 1) * sizeof(WCHAR)); + thisVar[endOfVar - start - 1] = L'\0'; + colonpos = wcschr(thisVar, L':');
/* If there's complex substitution, just need %var% for now - to get the expanded data to play with */ - if (colonpos) { - *colonpos = '%'; - savedchar = *(colonpos+1); - *(colonpos+1) = 0x00; - } + * to get the expanded data to play with + */ + if (colonpos) colonpos[0] = L'\0';
- /* By now, we know the variable we want to expand but it may be - surrounded by '!' if we are in delayed expansion - if so convert - to % signs. */ - if (startchar=='!') { - thisVar[0] = '%'; - thisVar[(endOfVar - start)] = '%'; - } - WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar)); - - /* Expand to contents, if unchanged, return */ - /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */ - /* override if existing env var called that name */ - if (WCMD_is_magic_envvar(thisVar, L"ERRORLEVEL")) { - len = wsprintfW(thisVarContents, L"%d", errorlevel); - } else if (WCMD_is_magic_envvar(thisVar, L"DATE")) { - len = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, - NULL, thisVarContents, ARRAY_SIZE(thisVarContents)); - } else if (WCMD_is_magic_envvar(thisVar, L"TIME")) { - len = GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, - NULL, thisVarContents, ARRAY_SIZE(thisVarContents)); - } else if (WCMD_is_magic_envvar(thisVar, L"CD")) { - len = GetCurrentDirectoryW(ARRAY_SIZE(thisVarContents), thisVarContents); - } else if (WCMD_is_magic_envvar(thisVar, L"RANDOM")) { - len = wsprintfW(thisVarContents, L"%d", rand() % 32768); - } else { - if ((len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents)))) len--; - } + TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
- if (len == 0) - return endOfVar+1; + /* env variables (when set) have priority over magic env variables */ + len = GetEnvironmentVariableW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents)); + if (!len) + { + if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"ERRORLEVEL", -1) == CSTR_EQUAL) + len = wsprintfW(thisVarContents, L"%d", errorlevel); + else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"DATE", -1) == CSTR_EQUAL) + len = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, + NULL, thisVarContents, ARRAY_SIZE(thisVarContents)); + else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"TIME", -1) == CSTR_EQUAL) + len = GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, + NULL, thisVarContents, ARRAY_SIZE(thisVarContents)); + else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"CD", -1) == CSTR_EQUAL) + len = GetCurrentDirectoryW(ARRAY_SIZE(thisVarContents), thisVarContents); + else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, thisVar, -1, L"RANDOM", -1) == CSTR_EQUAL) + len = wsprintfW(thisVarContents, L"%d", rand() % 32768); + } + /* Restore complex bit */ + if (colonpos) colonpos[0] = L':';
/* In a batch program, unknown env vars are replaced with nothing, - note syntax %garbage:1,3% results in anything after the ':' - except the % - From the command line, you just get back what you entered */ - if (lstrcmpiW(thisVar, thisVarContents) == 0) { - - /* Restore the complex part after the compare */ - if (colonpos) { - *colonpos = ':'; - *(colonpos+1) = savedchar; - } - - /* Command line - just ignore this */ - if (context == NULL || startchar == L'!') return endOfVar+1; - - /* Batch - replace unknown env var with nothing */ - if (colonpos == NULL) - return WCMD_strsubstW(start, endOfVar + 1, NULL, 0); - len = lstrlenW(thisVar); - thisVar[len-1] = 0x00; - /* If %:...% supplied, : is retained */ - if (colonpos == thisVar+1) - return WCMD_strsubstW(start, endOfVar + 1, colonpos, -1); - return WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1); + * note syntax %garbage:1,3% results in anything after the ':' + * except the % + * From the command line, you just get back what you entered + */ + if (!len) + { + /* Command line - just ignore this */ + if (context == NULL) return endOfVar + 1; + + /* Batch - replace unknown env var with nothing */ + if (colonpos == NULL) + return WCMD_strsubstW(start, endOfVar + 1, NULL, 0); + if (colonpos == thisVar) + return WCMD_strsubstW(start, endOfVar + 1, colonpos, -1); + return WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1); }
- /* See if we need to do complex substitution (any ':'s), if not - then our work here is done */ + /* See if we need to do complex substitution (any ':'s) */ if (colonpos == NULL) return WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
- /* Restore complex bit */ - *colonpos = ':'; - *(colonpos+1) = savedchar; - /* Handle complex substitutions: xxx=yyy (replace xxx with yyy) @@ -677,10 +598,10 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar) */
/* ~ is substring manipulation */ - if (savedchar == '~') { + if (colonpos[1] == L'~') {
int substrposition, substrlength = 0; - WCHAR *commapos = wcschr(colonpos+2, ','); + WCHAR *commapos = wcschr(colonpos+2, L','); WCHAR *startCopy;
substrposition = wcstol(colonpos+2, NULL, 10); @@ -707,7 +628,7 @@ static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar) return WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength); /* search and replace manipulation */ } else { - WCHAR *equalspos = wcsstr(colonpos, L"="); + WCHAR *equalspos = wcschr(colonpos, L'='); WCHAR *replacewith = equalspos+1; WCHAR *found = NULL; WCHAR *searchIn; @@ -859,7 +780,7 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) { /* Replace the 2 characters, % and for variable character */ p = WCMD_strsubstW(p, p + 2, forloopcontext->variable[forvaridx], -1); } else if (!atExecute || startchar == '!') { - p = WCMD_expand_envvar(p, startchar); + p = WCMD_expand_envvar(p);
/* In a FOR loop, see if this is the variable to replace */ } else { /* Ignore %'s on second pass of batch program */
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 6 +++--- programs/cmd/wcmdmain.c | 26 +++++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 137404c5a52..ae4f674f1ca 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1822,9 +1822,9 @@ bar cp1 echo bar cp2 echo @todo_wine@cp3 bar b1 echo -@todo_wine@bar b2 echo -@todo_wine@bar b3 echo -@todo_wine@bar cb1 echo +bar b2 echo +bar b3 echo +bar cb1 echo --- mixing batch and builtins bar@space@ foo diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 4c30094cc76..0c74011a029 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -743,15 +743,10 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) { wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p)); i = *(p+1) - '0';
- /* Don't touch %% unless it's in Batch */ - if (!atExecute && *(p+1) == startchar) { - if (context) { - WCMD_strsubstW(p, p+1, NULL, 0); - p++; - } - else p+=2; - } else if (*(p+1) == startchar && startchar == L'!') { - p++; + /* handle consecutive % or ! */ + if ((!atExecute || startchar == L'!') && p[1] == startchar) { + if (context) WCMD_strsubstW(p, p + 1, NULL, 0); + if (!context || startchar == L'%') p++; /* Replace %~ modifications if in batch program */ } else if (*(p+1) == '~') { WCMD_HandleTildeModifiers(&p, atExecute); @@ -776,12 +771,19 @@ static void handleExpansion(WCHAR *cmd, BOOL atExecute) {
} else { int forvaridx = for_var_char_to_index(*(p+1)); - if (startchar == '%' && forvaridx != -1 && forloopcontext->variable[forvaridx]) { + if (startchar == L'%' && forvaridx != -1 && forloopcontext->variable[forvaridx]) { /* Replace the 2 characters, % and for variable character */ p = WCMD_strsubstW(p, p + 2, forloopcontext->variable[forvaridx], -1); - } else if (!atExecute || startchar == '!') { + } else if (!atExecute || startchar == L'!') { + BOOL first = p == cmd; p = WCMD_expand_envvar(p); - + /* FIXME: maybe this more likely calls for a specific handling of first arg? */ + if (context && startchar == L'!' && first) + { + WCHAR *last; + for (last = p; *last == startchar; last++) {} + p = WCMD_strsubstW(p, last, NULL, 0); + } /* In a FOR loop, see if this is the variable to replace */ } else { /* Ignore %'s on second pass of batch program */ p++;
From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd | 9 +++++++++ programs/cmd/tests/test_builtins.cmd.exp | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index ef6c07e09fe..29f4ad9e9c4 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -2044,6 +2044,15 @@ for %%i in (test) do ( ) echo d4 ) +echo --- EXIT /B inside FOR loops +goto :after_exitBinsideForLoop +:exitBinsideForLoop +for /l %%i in (1,1,3) do ( + echo %%i + if %%i==2 exit /b 0 +) +:after_exitBinsideForLoop +call :exitBinsideForLoop echo --- set /a goto :testseta
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index ae4f674f1ca..9cbd248850b 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1335,7 +1335,10 @@ d3 a4 c4 d4 ---- set /a +--- EXIT /B inside FOR loops +1 +2 +@todo_wine@--- set /a ------ individual operations WINE_foo correctly 3 WINE_foo correctly -1
From: Eric Pouech epouech@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57147
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/cmd/wcmdmain.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 9cbd248850b..a58a0254e16 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1338,7 +1338,7 @@ d4 --- EXIT /B inside FOR loops 1 2 -@todo_wine@--- set /a +--- set /a ------ individual operations WINE_foo correctly 3 WINE_foo correctly -1 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 0c74011a029..be369686c00 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -3411,7 +3411,7 @@ static RETURN_CODE for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE RETURN_CODE return_code = NO_ERROR;
/* Read line by line until end of file */ - while (fgetws(buffer, ARRAY_SIZE(buffer), input)) + while (return_code != RETURN_CODE_ABORTED && fgetws(buffer, ARRAY_SIZE(buffer), input)) { size_t len;
@@ -3479,7 +3479,7 @@ static RETURN_CODE for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NO } else { - for (i = 0; ; i++) + for (i = 0; return_code != RETURN_CODE_ABORTED; i++) { WCHAR *element = WCMD_parameter(args, i, NULL, TRUE, FALSE); if (!element || !*element) break; @@ -3520,7 +3520,7 @@ static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHA
wcscpy(set, for_ctrl->set); handleExpansion(set, TRUE); - for (i = 0; ; i++) + for (i = 0; return_code != RETURN_CODE_ABORTED; i++) { WCHAR *element = WCMD_parameter(set, i, NULL, TRUE, FALSE); if (!element || !*element) break; @@ -3556,7 +3556,7 @@ static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHA wcscpy(&buffer[insert_pos], fd.cFileName); WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer); return_code = node_execute(node); - } while (FindNextFileW(hff, &fd) != 0); + } while (return_code != RETURN_CODE_ABORTED && FindNextFileW(hff, &fd) != 0); FindClose(hff); } else @@ -3585,7 +3585,7 @@ static RETURN_CODE for_control_execute_walk_files(CMD_FOR_CONTROL *for_ctrl, CMD else dirs_to_walk = WCMD_dir_stack_create(NULL, NULL); ref_len = wcslen(dirs_to_walk->dirName);
- while (dirs_to_walk) + while (return_code != RETURN_CODE_ABORTED && dirs_to_walk) { TRACE("About to walk %p %ls for %s\n", dirs_to_walk, dirs_to_walk->dirName, debugstr_for_control(for_ctrl)); if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) @@ -3624,7 +3624,7 @@ static RETURN_CODE for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NO }
for (var = numbers[0]; - (numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]; + return_code != RETURN_CODE_ABORTED && ((numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2]); var += numbers[1]) { WCHAR tmp[32];
From: Eric Pouech epouech@codeweavers.com
Native CMD.EXE has different behavior when running .BAT or .CMD file for some builtin commands.
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/tests/batch.c | 37 ++- programs/cmd/tests/rsrc.rc | 6 + programs/cmd/tests/test_builtins.bat | 319 +++++++++++++++++++++++ programs/cmd/tests/test_builtins.bat.exp | 223 ++++++++++++++++ programs/cmd/tests/test_builtins.cmd | 2 + 5 files changed, 577 insertions(+), 10 deletions(-) create mode 100644 programs/cmd/tests/test_builtins.bat create mode 100644 programs/cmd/tests/test_builtins.bat.exp
diff --git a/programs/cmd/tests/batch.c b/programs/cmd/tests/batch.c index d44d2d33dec..6bc5e916ea9 100644 --- a/programs/cmd/tests/batch.c +++ b/programs/cmd/tests/batch.c @@ -74,17 +74,21 @@ static const char* convert_input_data(const char *data, DWORD size, DWORD *new_s return new_data; }
-static BOOL run_cmd(const char *cmd_data, DWORD cmd_size) +static BOOL run_cmd(const char *res_name, const char *cmd_data, DWORD cmd_size) { SECURITY_ATTRIBUTES sa = {sizeof(sa), 0, TRUE}; - char command[] = "test.cmd"; + char command_cmd[] = "test.cmd", command_bat[] = "test.bat"; + char *command; STARTUPINFOA si = {sizeof(si)}; PROCESS_INFORMATION pi; HANDLE file,fileerr; DWORD size; BOOL bres;
- file = CreateFileA("test.cmd", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + command = (strlen(res_name) >= 4 && !stricmp(res_name + strlen(res_name) - 4, ".cmd")) ? + command_cmd : command_bat; + + file = CreateFileA(command, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); ok(file != INVALID_HANDLE_VALUE, "CreateFile failed\n"); if(file == INVALID_HANDLE_VALUE) @@ -123,7 +127,7 @@ static BOOL run_cmd(const char *cmd_data, DWORD cmd_size) CloseHandle(pi.hProcess); CloseHandle(file); CloseHandle(fileerr); - DeleteFileA("test.cmd"); + DeleteFileA(command); return TRUE; }
@@ -131,8 +135,21 @@ static DWORD map_file(const char *file_name, const char **ret) { HANDLE file, map; DWORD size; - - file = CreateFileA(file_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + unsigned retries; + + /* Even if .cmd/.bat wait on child process succeeded, there are cases where the + * output file is not closed yet (on Windows) (seems to only happen with .bat input files). + * So retry a couple of times before failing. + * Note: using file share option works at once, but we cannot be sure all + * output has been flushed. + */ + for (retries = 0; retries < 50; retries++) + { + file = CreateFileA(file_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + if (file != INVALID_HANDLE_VALUE || GetLastError() != ERROR_SHARING_VIOLATION) + break; + Sleep(1); + } ok(file != INVALID_HANDLE_VALUE, "CreateFile failed: %08lx\n", GetLastError()); if(file == INVALID_HANDLE_VALUE) return 0; @@ -358,7 +375,7 @@ static void test_output(const char *out_data, DWORD out_size, const char *exp_da ok(out_ptr >= out_data+out_size, "too long output, got additional %s\n", out_ptr); }
-static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, DWORD exp_size) +static void run_test(const char *res_name, const char *cmd_data, DWORD cmd_size, const char *exp_data, DWORD exp_size) { const char *out_data, *actual_cmd_data; DWORD out_size, actual_cmd_size; @@ -367,7 +384,7 @@ static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, if(!actual_cmd_size || !actual_cmd_data) goto cleanup;
- if(!run_cmd(actual_cmd_data, actual_cmd_size)) + if(!run_cmd(res_name, actual_cmd_data, actual_cmd_size)) goto cleanup;
out_size = map_file("test.out", &out_data); @@ -402,7 +419,7 @@ static void run_from_file(const char *file_name) return; }
- run_test(test_data, test_size, out_data, out_size); + run_test(file_name, test_data, test_size, out_data, out_size);
UnmapViewOfFile(test_data); UnmapViewOfFile(out_data); @@ -445,7 +462,7 @@ static BOOL WINAPI test_enum_proc(HMODULE module, LPCSTR type, LPSTR name, LONG_ if(!out_size) return TRUE;
- run_test(cmd_data, cmd_size, out_data, out_size); + run_test(name, cmd_data, cmd_size, out_data, out_size); return TRUE; }
diff --git a/programs/cmd/tests/rsrc.rc b/programs/cmd/tests/rsrc.rc index affe04c2bdd..0336863963f 100644 --- a/programs/cmd/tests/rsrc.rc +++ b/programs/cmd/tests/rsrc.rc @@ -22,6 +22,12 @@ test_builtins.cmd TESTCMD "test_builtins.cmd" /* @makedep: test_builtins.cmd.exp */ test_builtins.cmd.exp TESTOUT "test_builtins.cmd.exp"
+/* @makedep: test_builtins.bat */ +test_builtins.bat TESTCMD "test_builtins.bat" + +/* @makedep: test_builtins.bat.exp */ +test_builtins.bat.exp TESTOUT "test_builtins.bat.exp" + /* @makedep: test_cmdline.cmd */ test_cmdline.cmd TESTCMD "test_cmdline.cmd"
diff --git a/programs/cmd/tests/test_builtins.bat b/programs/cmd/tests/test_builtins.bat new file mode 100644 index 00000000000..a95c94c5ad8 --- /dev/null +++ b/programs/cmd/tests/test_builtins.bat @@ -0,0 +1,319 @@ +echo Tests for cmd's builtin commands BAT +@echo off +setlocal EnableDelayedExpansion + +rem All the success/failure tests are meant to be duplicated in test_builtins.cmd +rem So be sure to update both files at once + +echo --- success/failure for basics +call :setError 0 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :setError 33 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :setError 666 & (echo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (echo foo >> h:\i\dont\exist\at\all.txt &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & echo foo >> h:\i\dont\exist\at\all.txt & echo ERRORLEVEL !errorlevel! +echo --- success/failure for IF/FOR blocks +call :setError 666 & ((if 1==1 echo "">NUL) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((if 1==0 echo "">NUL) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((if 1==1 (call :setError 33)) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((if 1==0 (call :setError 33) else call :setError 34) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((for %%i in () do echo "") &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((for %%i in () do call :setError 33) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((for %%i in (a) do call :setError 0) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((for %%i in (a) do call :setError 33) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for external command +mkdir foo & cd foo +call :setError 666 & (I\dont\exist.exe &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & I\dont\exist.exe & echo ERRORLEVEL !errorlevel! +call :setError 666 & (Idontexist.exe &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & Idontexist.exe & echo ERRORLEVEL !errorlevel! +call :setError 666 & (cmd.exe /c "echo foo & exit /b 0" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cmd.exe /c "echo foo & exit /b 1024" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (I\dont\exist.html &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem can't run this test, generates a nice popup under windows +rem echo:>foobar.IDontExist +rem call :setError 666 & (foobar.IDontExist &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo +echo --- success/failure for CALL command +mkdir foo & cd foo +echo exit /b %%1 > foobar.bat +rem call :setError 666 & (call I\dont\exist.exe &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem terminates batch exec on native... +call :setError 666 & (call Idontexist.exe &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call .\foobar.bat 0 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call .\foobar.bat 1024 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call cmd.exe /c "echo foo & exit /b 0" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call cmd.exe /c "echo foo & exit /b 1025" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call rmdir foobar.dir &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo +echo --- success/failure for pipes +call :setError 666 & ((echo a | echo b) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo a | call :setError 34) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((call :setError 33 | echo a) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo a | rmdir I\dont\exist\at\all) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((rmdir I\dont\exist\at\all | echo a) &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem in a pipe, if LHS or RHS can't be started, the whole cmd is abandonned (not just the pipe!!) +echo ^( %%1 ^| %%2 ^) > foo.bat +echo echo AFTER %%ERRORLEVEL%% >> foo.bat +call :setError 666 & (cmd.exe /q /c "call foo.bat echo I\dont\exist.exe" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cmd.exe /q /c "call foo.bat I\dont\exist.exe echo" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +erase /q foo.bat +echo --- success/failure for START command +call :setError 666 & (start "" /foobar >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem call :setError 666 & (start /B I\dont\exist.exe &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem can't run this test, generates a nice popup under windows +call :setError 666 & (start "" /B /WAIT cmd.exe /c "echo foo & exit /b 1024" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (start "" /B cmd.exe /c "(choice /C:YN /T:3 /D:Y > NUL) & exit /b 1024" &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for TYPE command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +call :setError 666 & (type &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (type NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (type i\dont\exist\at\all.txt &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (type file* i\dont\exist\at\all.txt &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- +call :setError 666 & (type i\dont\exist\at\all.txt file* &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for COPY command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +call :setError 666 & (copy fileA >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (copy fileA fileZ >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (copy fileA fileZ /-Y >NUL <NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (copy fileA+fileD fileZ /-Y >NUL <NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (copy fileD+fileA fileZ /-Y >NUL <NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +if exist fileD echo Unexpected fileD +cd .. && rd /q /s foo + +echo --- success/failure for MOVE command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +call :setError 666 & (move >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (move fileA fileC >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (move fileC nowhere\fileC >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (move fileD fileE >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (move fileC fileB /-Y >NUL <NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for RENAME command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +call :setError 666 & (rename fileB >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rename fileB fileA >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rename fileB nowhere\fileB >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rename fileD fileC >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rename fileB fileC >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for ERASE command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +echo e > fileE +call :setError 666 & (erase &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (erase fileE &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (erase i\dont\exist\at\all.txt &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (erase file* i\dont\exist\at\all.txt &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for change drive command +pushd C:\ +call :setError 666 & (c: &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (1: &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call c: &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (call 1: &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +popd + +echo --- success/failure for MKDIR,MD command +mkdir foo & cd foo +call :setError 666 & (mkdir &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mkdir abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mkdir abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mkdir @:\cba\abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mkdir NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for CD command +mkdir foo & cd foo +mkdir abc +call :setError 666 & (cd abc >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cd abc >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cd .. >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cd >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for PUSHD/POPD commands +mkdir foo & cd foo +mkdir abc +call :setError 666 & (pushd &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (pushd abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (pushd abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (popd abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (popd &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & popd & echo ERRORLEVEL !errorlevel! +cd .. && rd /q /s foo + +echo --- success/failure for DIR command +mkdir foo & cd foo +echo a > fileA +echo b > fileB +mkdir dir +echo b > dir\fileB +call :setError 666 & (dir /e >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (dir zzz >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (dir fileA zzz >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (dir zzz fileA >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (dir dir\zzz >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (dir file* >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo +echo --- success/failure for RMDIR/RD command +mkdir foo & cd foo +mkdir abc +call :setError 666 & (rmdir &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo "">abc\abc +call :setError 666 & (rmdir abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rmdir abc\abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +erase abc\abc +call :setError 666 & (rmdir abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rmdir abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (rmdir @:\cba\abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo +mkdir foo & cd foo +mkdir abc +call :setError 666 & rmdir & echo ERRORLEVEL !errorlevel! +echo "">abc\abc +call :setError 666 & rmdir abc & echo ERRORLEVEL !errorlevel! +call :setError 666 & rmdir abc\abc & echo ERRORLEVEL !errorlevel! +erase abc\abc +call :setError 666 & rmdir abc & echo ERRORLEVEL !errorlevel! +call :setError 666 & rmdir abc & echo ERRORLEVEL !errorlevel! +call :setError 666 & rmdir @:\cba\abc & echo ERRORLEVEL !errorlevel! +cd .. && rd /q /s foo + +echo --- success/failure for MKLINK command +mkdir foo & cd foo +call :setError 666 & (mklink &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /h foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /z foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo bar > foo +call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /h bar foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /h bar foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +cd .. && rd /q /s foo + +echo --- success/failure for SETLOCAL/ENDLOCAL commands +call :setError 666 & (setlocal foobar &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (setlocal &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (endlocal foobar &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (setlocal DisableExtensions &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (setlocal EnableExtensions &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for DATE command +call :setError 666 & (date /t >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (date AAAA >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem need evelated priviledges to set the date +echo --- success/failure for TIME command +call :setError 666 & (time /t >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (time AAAA >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem need evelated priviledges to set the time +echo --- success/failure for BREAK command +call :setError 666 & (break &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (break 345 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for VER command +call :setError 666 & (ver >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (ver foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (ver /f >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for VERIFY command +call :setError 666 & (verify >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (verify on >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (verify foobar >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for VOL command +call :setError 666 & (vol >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (vol c: >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (vol foobar >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (vol /Z >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for LABEL command +call :setError 666 & (<NUL label >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem need evelated priviledges to test + +echo --- success/failure for PATH command +call :setError 666 & (path >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +set SAVED_PATH=%PATH% > NUL +call :setError 666 & (path @:\I\dont\Exist &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +path +call :setError 666 & (path ; &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +path +call :setError 666 & (path !SAVED_PATH! &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +set "SAVED_PATH=" +echo --- success/failure for SET command +call :setError 666 & (set >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set "SAVED_PATH=%PATH%" >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set S >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set "SAVED_PATH=" >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set "SAVED_PATH=" >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set /Q >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (set ThisVariableLikelyDoesntExist >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem missing /A and /p tests +echo --- success/failure for ASSOC command +call :setError 666 & (assoc >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (assoc cmd >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (assoc .idontexist >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem testing changing the assoc requires elevated privilege +echo --- success/failure for FTYPE command +call :setError 666 & (ftype >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (ftype cmdfile >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (ftype fileidontexist >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem testing changing the ftype requires elevated privilege +echo --- success/failure for SHIFT command +call :setError 666 & shift /abc &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :testSuccessFailureShift 1 +goto :afterSuccessFailureShift +:testSuccessFailureShift +call :setError 666 & shift &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :setError 666 & shift &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +goto :eof +:afterSuccessFailureShift +echo --- success/failure for HELP command +call :setError 666 & help >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :setError 666 & help dir >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +call :setError 666 & help ACommandThatLikelyDoesntExist >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +echo --- success/failure for PROMPT command +call :setError 666 & prompt >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! +rem doesn't seem to set errors either on invalid $ escapes, nor qualifiers + +echo --- success/failure for CLS command +call :setError 666 & (cls &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cls foobar &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (cls /X &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for COLOR command +call :setError 666 & (color fc < NUL > NUL 2>&1 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem TODO: color is also hard to test: it requires fd 1 to be bound to a console, so we can't redirect its output +echo --- success/failure for TITLE command +call :setError 666 & (title a new title &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (title &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for CHOICE command +call :setError 666 & (choice <NUL >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (choice /c <NUL >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & ((echo A | choice /C:BA) >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (choice /C:BA <NUL >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem syntax errors in command return INVALID_FUNCTION, need to find a test for returning 255 +echo --- success/failure for MORE command +call :setError 666 & (more NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (more I\dont\exist.txt > NUL 2>&1 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (echo foo | more &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +echo --- success/failure for PAUSE command +call :setError 666 & (pause < NUL > NUL 2>&1 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rem TODO: pause is harder to test when fd 1 is a console handle as we don't control output +echo --- +setlocal DisableDelayedExpansion + +goto :eof + +rem Subroutine to set errorlevel and return +:setError +exit /B %1 +rem This line runs under cmd in windows NT 4, but not in more modern versions. diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp new file mode 100644 index 00000000000..12dd22effed --- /dev/null +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -0,0 +1,223 @@ + +@pwd@>echo Tests for cmd's builtin commands BAT@space@ +Tests for cmd's builtin commands BAT +--- success/failure for basics +SUCCESS 0 +FAILURE 33 +foo@space@ +SUCCESS 666 +FAILURE 1 +ERRORLEVEL 666 +--- success/failure for IF/FOR blocks +SUCCESS 666 +SUCCESS 666 +FAILURE 33 +FAILURE 34 +SUCCESS 666 +SUCCESS 666 +SUCCESS 0 +FAILURE 33 +--- success/failure for external command +FAILURE 1 +@todo_wine@ERRORLEVEL 3 +FAILURE 1 +ERRORLEVEL 9009 +foo@space@ +SUCCESS 0 +foo@space@ +FAILURE 1024 +FAILURE 1 +--- success/failure for CALL command +FAILURE 1 +SUCCESS 0 +FAILURE 1024 +foo@space@ +SUCCESS 0 +foo@space@ +FAILURE 1025 +FAILURE 2 +--- success/failure for pipes +b +SUCCESS 0 +FAILURE 1 +a +SUCCESS 0 +FAILURE 3 +a +SUCCESS 0 +FAILURE 255 +FAILURE 255 +--- success/failure for START command +@todo_wine@FAILURE 1 +foo@space@ +SUCCESS 1024 +@todo_wine@SUCCESS 666 +--- success/failure for TYPE command +FAILURE 1 +SUCCESS 0 +FAILURE 1 +@todo_wine@a@space@ +@todo_wine@b@space@ +@todo_wine@FAILURE 1 +@todo_wine@--- +FAILURE 1 +--- success/failure for COPY command +FAILURE 1 +SUCCESS 0 +FAILURE 1 +FAILURE 1 +FAILURE 1 +--- success/failure for MOVE command +FAILURE 1 +SUCCESS 0 +FAILURE 1 +FAILURE 1 +FAILURE 1 +--- success/failure for RENAME command +FAILURE 1 +FAILURE 1 +FAILURE 1 +FAILURE 1 +SUCCESS 0 +--- success/failure for ERASE command +FAILURE 1 +SUCCESS 0 +FAILURE 1 +FAILURE 1 +--- success/failure for change drive command +SUCCESS 666 +FAILURE 1 +SUCCESS 0 +FAILURE 1 +--- success/failure for MKDIR,MD command +FAILURE 1 +SUCCESS 0 +FAILURE 1 +FAILURE 1 +SUCCESS 0 +--- success/failure for CD command +SUCCESS 0 +FAILURE 1 +SUCCESS 0 +SUCCESS 0 +--- success/failure for PUSHD/POPD commands +SUCCESS 0 +SUCCESS 0 +FAILURE 1 +SUCCESS 666 +FAILURE 1 +ERRORLEVEL 666 +--- success/failure for DIR command +FAILURE 1 +FAILURE 1 +SUCCESS 0 +SUCCESS 0 +FAILURE 1 +SUCCESS 0 +--- success/failure for RMDIR/RD command +FAILURE 1 +FAILURE 145 +FAILURE 267 +SUCCESS 666 +FAILURE 2 +FAILURE 3 +ERRORLEVEL 666 +ERRORLEVEL 666 +ERRORLEVEL 666 +ERRORLEVEL 666 +ERRORLEVEL 666 +ERRORLEVEL 666 +--- success/failure for MKLINK command +FAILURE 1 +FAILURE 1 +FAILURE 1 +FAILURE 1 +FAILURE 1 +SUCCESS 0 +FAILURE 1 +--- success/failure for SETLOCAL/ENDLOCAL commands +FAILURE 1 +SUCCESS 0 +SUCCESS 666 +@todo_wine@SUCCESS@space@ +SUCCESS 0 +--- success/failure for DATE command +SUCCESS 0 +FAILURE 1 +--- success/failure for TIME command +SUCCESS 0 +FAILURE 1 +--- success/failure for BREAK command +@todo_wine@SUCCESS 666 +@todo_wine@SUCCESS 666 +--- success/failure for VER command +SUCCESS 0 +SUCCESS 0 +FAILURE 1 +--- success/failure for VERIFY command +SUCCESS 0 +SUCCESS 0 +FAILURE 1 +--- success/failure for VOL command +SUCCESS 0 +SUCCESS 0 +FAILURE 1 +FAILURE 1 +--- success/failure for LABEL command +FAILURE 1 +--- success/failure for PATH command +@todo_wine@SUCCESS 666 +@todo_wine@SUCCESS 666 +PATH=@:\I\dont\Exist@space@ +@todo_wine@SUCCESS 666 +PATH=(null) +@todo_wine@SUCCESS 666 +--- success/failure for SET command +@todo_wine@SUCCESS 666 +@todo_wine@SUCCESS 666 +FAILURE 1 +@todo_wine@SUCCESS 666 +@todo_wine@SUCCESS 666 +FAILURE 1 +FAILURE 1 +--- success/failure for ASSOC command +@todo_wine@SUCCESS 666 +FAILURE 1 +FAILURE 1 +--- success/failure for FTYPE command +@todo_wine@SUCCESS 666 +FAILURE 2 +FAILURE 2 +--- success/failure for SHIFT command +FAILURE 1 +SUCCESS 666 +SUCCESS 666 +--- success/failure for HELP command +FAILURE 1 +FAILURE 1 +SUCCESS 0 +--- success/failure for PROMPT command +@todo_wine@SUCCESS 666 +--- success/failure for CLS command +@todo_wine@@formfeed@SUCCESS 666 +@todo_wine@@formfeed@SUCCESS 666 +FAILURE 1 +--- success/failure for COLOR command +FAILURE 1 +--- success/failure for TITLE command +SUCCESS 666 +SUCCESS 666 +--- success/failure for CHOICE command +FAILURE 1 +FAILURE 1 +FAILURE 2 +FAILURE 1 +--- success/failure for MORE command +SUCCESS 0 +SUCCESS 0 +foo@space@ + +SUCCESS 0 +--- success/failure for PAUSE command +FAILURE 1 +--- diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 29f4ad9e9c4..01c8a73ff31 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -467,6 +467,8 @@ if 1==0 (echo q1) else echo q2&echo q3 echo ------------- Testing internal commands return codes setlocal EnableDelayedExpansion
+rem All the success/failure tests are meant to be duplicated in test_builtins.bat +rem So be sure to update both files at once echo --- success/failure for basics call :setError 0 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel! call :setError 33 &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!
From: Eric Pouech epouech@codeweavers.com
Some builtin commands don't set errorlevel upon success when run from a .bat while (while it's set when run from a .cmd file).
ASSOC, FTYPE, PATH, PROMPT, SET
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57139
Signed-off-by: Eric Pouech epouech@codeweavers.com --- programs/cmd/builtins.c | 38 +++++++++++++----------- programs/cmd/tests/test_builtins.bat.exp | 22 +++++++------- programs/cmd/wcmd.h | 12 +++++++- 3 files changed, 42 insertions(+), 30 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index ac4ed0ab049..7a7562d60d5 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -3279,7 +3279,8 @@ RETURN_CODE WCMD_setshow_env(WCHAR *s) } else if (!status) WCMD_print_error(); } } - return errorlevel = return_code; + return WCMD_is_in_context(L".bat") && return_code == NO_ERROR ? + return_code : (errorlevel = return_code); }
/**************************************************************************** @@ -3308,7 +3309,7 @@ RETURN_CODE WCMD_setshow_path(const WCHAR *args) return errorlevel = ERROR_INVALID_FUNCTION; } } - return errorlevel = NO_ERROR; + return WCMD_is_in_context(L".bat") ? NO_ERROR : (errorlevel = NO_ERROR); }
/**************************************************************************** @@ -3333,7 +3334,7 @@ RETURN_CODE WCMD_setshow_prompt(void) } else SetEnvironmentVariableW(L"PROMPT", s); } - return errorlevel = NO_ERROR; + return WCMD_is_in_context(L".bat") ? NO_ERROR : (errorlevel = NO_ERROR); }
/**************************************************************************** @@ -3882,16 +3883,17 @@ RETURN_CODE WCMD_exit(void) */ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc) { - HKEY key; - DWORD accessOptions = KEY_READ; - WCHAR *newValue; - LONG rc = ERROR_SUCCESS; - WCHAR keyValue[MAXSTRING]; - DWORD valueLen; - HKEY readKey; + RETURN_CODE return_code; + HKEY key; + DWORD accessOptions = KEY_READ; + WCHAR *newValue; + LONG rc = ERROR_SUCCESS; + WCHAR keyValue[MAXSTRING]; + DWORD valueLen; + HKEY readKey;
/* See if parameter includes '=' */ - errorlevel = NO_ERROR; + return_code = NO_ERROR; newValue = wcschr(args, '='); if (newValue) accessOptions |= KEY_WRITE;
@@ -3966,7 +3968,7 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc) WCMD_output_asis(keyValue); WCMD_output_asis(L"\r\n"); RegCloseKey(readKey); - errorlevel = NO_ERROR; + return_code = NO_ERROR; } else { WCHAR msgbuffer[MAXSTRING];
@@ -3977,7 +3979,7 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc) LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer)); } WCMD_output_stderr(msgbuffer, keyValue); - errorlevel = assoc ? ERROR_INVALID_FUNCTION : ERROR_FILE_NOT_FOUND; + return_code = assoc ? ERROR_INVALID_FUNCTION : ERROR_FILE_NOT_FOUND; }
/* Not a query - it's a set or clear of a value */ @@ -4010,7 +4012,7 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc)
} else if (rc != ERROR_FILE_NOT_FOUND) { WCMD_print_error(); - errorlevel = ERROR_FILE_NOT_FOUND; + return_code = ERROR_FILE_NOT_FOUND;
} else { WCHAR msgbuffer[MAXSTRING]; @@ -4022,7 +4024,7 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc) LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer)); } WCMD_output_stderr(msgbuffer, args); - errorlevel = ERROR_FILE_NOT_FOUND; + return_code = ERROR_FILE_NOT_FOUND; }
/* It really is a set value = contents */ @@ -4038,7 +4040,7 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc)
if (rc != ERROR_SUCCESS) { WCMD_print_error(); - errorlevel = ERROR_FILE_NOT_FOUND; + return_code = ERROR_FILE_NOT_FOUND; } else { WCMD_output_asis(args); WCMD_output_asis(L"="); @@ -4051,8 +4053,8 @@ RETURN_CODE WCMD_assoc(const WCHAR *args, BOOL assoc)
/* Clean up */ RegCloseKey(key); - - return errorlevel; + return WCMD_is_in_context(L".bat") && return_code == NO_ERROR ? + return_code : (errorlevel = return_code); }
/**************************************************************************** diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp index 12dd22effed..a2cbe0cecd7 100644 --- a/programs/cmd/tests/test_builtins.bat.exp +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -166,26 +166,26 @@ FAILURE 1 --- success/failure for LABEL command FAILURE 1 --- success/failure for PATH command -@todo_wine@SUCCESS 666 -@todo_wine@SUCCESS 666 +SUCCESS 666 +SUCCESS 666 PATH=@:\I\dont\Exist@space@ -@todo_wine@SUCCESS 666 +SUCCESS 666 PATH=(null) -@todo_wine@SUCCESS 666 +SUCCESS 666 --- success/failure for SET command -@todo_wine@SUCCESS 666 -@todo_wine@SUCCESS 666 +SUCCESS 666 +SUCCESS 666 FAILURE 1 -@todo_wine@SUCCESS 666 -@todo_wine@SUCCESS 666 +SUCCESS 666 +SUCCESS 666 FAILURE 1 FAILURE 1 --- success/failure for ASSOC command -@todo_wine@SUCCESS 666 +SUCCESS 666 FAILURE 1 FAILURE 1 --- success/failure for FTYPE command -@todo_wine@SUCCESS 666 +SUCCESS 666 FAILURE 2 FAILURE 2 --- success/failure for SHIFT command @@ -197,7 +197,7 @@ FAILURE 1 FAILURE 1 SUCCESS 0 --- success/failure for PROMPT command -@todo_wine@SUCCESS 666 +SUCCESS 666 --- success/failure for CLS command @todo_wine@@formfeed@SUCCESS 666 @todo_wine@@formfeed@SUCCESS 666 diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h index 806b01d3393..ecb729efa00 100644 --- a/programs/cmd/wcmd.h +++ b/programs/cmd/wcmd.h @@ -340,7 +340,17 @@ extern BATCH_CONTEXT *context; extern FOR_CONTEXT *forloopcontext; extern BOOL delayedsubst;
-#endif /* !RC_INVOKED */ +static inline BOOL WCMD_is_in_context(const WCHAR *ext) +{ + size_t c_len, e_len; + if (!context) return FALSE; + if (!ext) return TRUE; + c_len = wcslen(context->batchfileW); + e_len = wcslen(ext); + return (c_len > e_len) && !wcsicmp(&context->batchfileW[c_len - e_len], ext); +} + + #endif /* !RC_INVOKED */
/* * Serial nos of builtin commands. These constants must be in step with
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=148531
Your paranoid android.
=== debian11b (64 bit WoW report) ===
dinput: joystick8.c:5762: Test failed: input 1: WaitForSingleObject returned 0x102 joystick8.c:5763: Test failed: input 1: got 0 WM_INPUT messages joystick8.c:5766: Test failed: input 1: got dwType 0 joystick8.c:5767: Test failed: input 1: got header.dwSize 0 joystick8.c:5769: Test failed: input 1: got hDevice 0000000000000000 joystick8.c:5771: Test failed: input 1: got dwSizeHid 0 joystick8.c:5772: Test failed: input 1: got dwCount 0