shell32/tests: Use the available ARRAY_SIZE() macro.
[wine.git] / dlls / shell32 / tests / shlexec.c
blobc67cc46a4619f4e33473454ed4d818794ba7acba
1 /*
2 * Unit test of the ShellExecute function.
4 * Copyright 2005, 2016 Francois Gouget for CodeWeavers
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 /* TODO:
22 * - test the default verb selection
23 * - test selection of an alternate class
24 * - try running executables in more ways
25 * - try passing arguments to executables
26 * - ShellExecute("foo.shlexec") with no path should work if foo.shlexec is
27 * in the PATH
28 * - test associations that use %l, %L or "%1" instead of %1
29 * - ShellExecuteEx() also calls SetLastError() with meaningful values which
30 * we could check
33 /* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */
34 #define NTDDI_WINXPSP1 0x05010100
35 #define NTDDI_VERSION NTDDI_WINXPSP1
36 #define _WIN32_WINNT 0x0501
38 #include <stdio.h>
39 #include <assert.h>
41 #include "wtypes.h"
42 #include "winbase.h"
43 #include "windef.h"
44 #include "shellapi.h"
45 #include "shlwapi.h"
46 #include "ddeml.h"
48 #include "wine/heap.h"
49 #include "wine/test.h"
51 #include "shell32_test.h"
54 static char argv0[MAX_PATH];
55 static int myARGC;
56 static char** myARGV;
57 static char tmpdir[MAX_PATH];
58 static char child_file[MAX_PATH];
59 static DLLVERSIONINFO dllver;
60 static BOOL skip_shlexec_tests = FALSE;
61 static BOOL skip_noassoc_tests = FALSE;
62 static HANDLE dde_ready_event;
65 /***
67 * Helpers to read from / write to the child process results file.
68 * (borrowed from dlls/kernel32/tests/process.c)
70 ***/
72 static const char* encodeA(const char* str)
74 static char encoded[2*1024+1];
75 char* ptr;
76 size_t len,i;
78 if (!str) return "";
79 len = strlen(str) + 1;
80 if (len >= sizeof(encoded)/2)
82 fprintf(stderr, "string is too long!\n");
83 assert(0);
85 ptr = encoded;
86 for (i = 0; i < len; i++)
87 sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
88 ptr[2 * len] = '\0';
89 return ptr;
92 static unsigned decode_char(char c)
94 if (c >= '0' && c <= '9') return c - '0';
95 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
96 assert(c >= 'A' && c <= 'F');
97 return c - 'A' + 10;
100 static char* decodeA(const char* str)
102 static char decoded[1024];
103 char* ptr;
104 size_t len,i;
106 len = strlen(str) / 2;
107 if (!len--) return NULL;
108 if (len >= sizeof(decoded))
110 fprintf(stderr, "string is too long!\n");
111 assert(0);
113 ptr = decoded;
114 for (i = 0; i < len; i++)
115 ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
116 ptr[len] = '\0';
117 return ptr;
120 static void WINETEST_PRINTF_ATTR(2,3) childPrintf(HANDLE h, const char* fmt, ...)
122 va_list valist;
123 char buffer[1024];
124 DWORD w;
126 va_start(valist, fmt);
127 vsprintf(buffer, fmt, valist);
128 va_end(valist);
129 WriteFile(h, buffer, strlen(buffer), &w, NULL);
132 static char* getChildString(const char* sect, const char* key)
134 char buf[1024];
135 char* ret;
137 GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
138 if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
139 assert(!(strlen(buf) & 1));
140 ret = decodeA(buf);
141 return ret;
145 /***
147 * Child code
149 ***/
151 #define CHILD_DDE_TIMEOUT 2500
152 static DWORD ddeInst;
153 static HSZ hszTopic;
154 static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH];
155 static BOOL post_quit_on_execute;
157 /* Handle DDE for doChild() and test_dde_default_app() */
158 static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv,
159 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
160 ULONG_PTR dwData1, ULONG_PTR dwData2)
162 DWORD size = 0;
164 if (winetest_debug > 2)
165 trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
166 uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
168 switch (uType)
170 case XTYP_CONNECT:
171 if (!DdeCmpStringHandles(hsz1, hszTopic))
173 size = DdeQueryStringA(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI);
174 ok(size < MAX_PATH, "got size %d\n", size);
175 assert(size < MAX_PATH);
176 return (HDDEDATA)TRUE;
178 return (HDDEDATA)FALSE;
180 case XTYP_EXECUTE:
181 size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0);
182 ok(size < MAX_PATH, "got size %d\n", size);
183 assert(size < MAX_PATH);
184 DdeFreeDataHandle(hData);
185 if (post_quit_on_execute)
186 PostQuitMessage(0);
187 return (HDDEDATA)DDE_FACK;
189 default:
190 return NULL;
194 static HANDLE hEvent;
195 static void init_event(const char* child_file)
197 char* event_name;
198 event_name=strrchr(child_file, '\\')+1;
199 hEvent=CreateEventA(NULL, FALSE, FALSE, event_name);
203 * This is just to make sure the child won't run forever stuck in a
204 * GetMessage() loop when DDE fails for some reason.
206 static void CALLBACK childTimeout(HWND wnd, UINT msg, UINT_PTR timer, DWORD time)
208 trace("childTimeout called\n");
210 PostQuitMessage(0);
213 static void doChild(int argc, char** argv)
215 char *filename, buffer[MAX_PATH];
216 HANDLE hFile, map;
217 int i;
218 UINT_PTR timer;
220 filename=argv[2];
221 hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
222 if (hFile == INVALID_HANDLE_VALUE)
223 return;
225 /* Arguments */
226 childPrintf(hFile, "[Child]\r\n");
227 if (winetest_debug > 2)
229 trace("cmdlineA='%s'\n", GetCommandLineA());
230 trace("argcA=%d\n", argc);
232 childPrintf(hFile, "cmdlineA=%s\r\n", encodeA(GetCommandLineA()));
233 childPrintf(hFile, "argcA=%d\r\n", argc);
234 for (i = 0; i < argc; i++)
236 if (winetest_debug > 2)
237 trace("argvA%d='%s'\n", i, argv[i]);
238 childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i]));
240 GetModuleFileNameA(GetModuleHandleA(NULL), buffer, sizeof(buffer));
241 childPrintf(hFile, "longPath=%s\r\n", encodeA(buffer));
243 /* Check environment variable inheritance */
244 *buffer = '\0';
245 SetLastError(0);
246 GetEnvironmentVariableA("ShlexecVar", buffer, sizeof(buffer));
247 childPrintf(hFile, "ShlexecVarLE=%d\r\n", GetLastError());
248 childPrintf(hFile, "ShlexecVar=%s\r\n", encodeA(buffer));
250 map = OpenFileMappingA(FILE_MAP_READ, FALSE, "winetest_shlexec_dde_map");
251 if (map != NULL)
253 HANDLE dde_ready;
254 char *shared_block = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 4096);
255 CloseHandle(map);
256 if (shared_block[0] != '\0' || shared_block[1] != '\0')
258 HDDEDATA hdde;
259 HSZ hszApplication;
260 MSG msg;
261 UINT rc;
263 post_quit_on_execute = TRUE;
264 ddeInst = 0;
265 rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
266 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
267 ok(rc == DMLERR_NO_ERROR, "DdeInitializeA() returned %d\n", rc);
268 hszApplication = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
269 ok(hszApplication != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
270 shared_block += strlen(shared_block) + 1;
271 hszTopic = DdeCreateStringHandleA(ddeInst, shared_block, CP_WINANSI);
272 ok(hszTopic != NULL, "DdeCreateStringHandleA(%s) = NULL\n", shared_block);
273 hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
274 ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
276 timer = SetTimer(NULL, 0, CHILD_DDE_TIMEOUT, childTimeout);
278 dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
279 SetEvent(dde_ready);
280 CloseHandle(dde_ready);
282 while (GetMessageA(&msg, NULL, 0, 0))
284 if (winetest_debug > 2)
285 trace("msg %d lParam=%ld wParam=%lu\n", msg.message, msg.lParam, msg.wParam);
286 DispatchMessageA(&msg);
289 Sleep(500);
290 KillTimer(NULL, timer);
291 hdde = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
292 ok(hdde != NULL, "DdeNameService() failed le=%u\n", GetLastError());
293 ok(DdeFreeStringHandle(ddeInst, hszTopic), "DdeFreeStringHandle(topic)\n");
294 ok(DdeFreeStringHandle(ddeInst, hszApplication), "DdeFreeStringHandle(application)\n");
295 ok(DdeUninitialize(ddeInst), "DdeUninitialize() failed\n");
297 else
299 dde_ready = OpenEventA(EVENT_MODIFY_STATE, FALSE, "winetest_shlexec_dde_ready");
300 SetEvent(dde_ready);
301 CloseHandle(dde_ready);
304 UnmapViewOfFile(shared_block);
306 childPrintf(hFile, "ddeExec=%s\r\n", encodeA(ddeExec));
309 childPrintf(hFile, "Failures=%d\r\n", winetest_get_failures());
310 CloseHandle(hFile);
312 init_event(filename);
313 SetEvent(hEvent);
314 CloseHandle(hEvent);
317 static void dump_child_(const char* file, int line)
319 if (winetest_debug > 1)
321 char key[18];
322 char* str;
323 int i, c;
325 str=getChildString("Child", "cmdlineA");
326 trace_(file, line)("cmdlineA='%s'\n", str);
327 c=GetPrivateProfileIntA("Child", "argcA", -1, child_file);
328 trace_(file, line)("argcA=%d\n",c);
329 for (i=0;i<c;i++)
331 sprintf(key, "argvA%d", i);
332 str=getChildString("Child", key);
333 trace_(file, line)("%s='%s'\n", key, str);
336 c=GetPrivateProfileIntA("Child", "ShlexecVarLE", -1, child_file);
337 trace_(file, line)("ShlexecVarLE=%d\n", c);
338 str=getChildString("Child", "ShlexecVar");
339 trace_(file, line)("ShlexecVar='%s'\n", str);
341 c=GetPrivateProfileIntA("Child", "Failures", -1, child_file);
342 trace_(file, line)("Failures=%d\n", c);
347 /***
349 * Helpers to check the ShellExecute() / child process results.
351 ***/
353 static char shell_call[2048];
354 static void WINETEST_PRINTF_ATTR(2,3) _okShell(int condition, const char *msg, ...)
356 va_list valist;
357 char buffer[2048];
359 strcpy(buffer, shell_call);
360 strcat(buffer, " ");
361 va_start(valist, msg);
362 vsprintf(buffer+strlen(buffer), msg, valist);
363 va_end(valist);
364 winetest_ok(condition, "%s", buffer);
366 #define okShell_(file, line) (winetest_set_location(file, line), 0) ? (void)0 : _okShell
367 #define okShell okShell_(__FILE__, __LINE__)
369 static char assoc_desc[2048];
370 static void reset_association_description(void)
372 *assoc_desc = '\0';
375 static void okChildString_(const char* file, int line, const char* key, const char* expected, const char* bad)
377 char* result;
378 result=getChildString("Child", key);
379 if (!result)
381 okShell_(file, line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
382 return;
384 okShell_(file, line)(lstrcmpiA(result, expected) == 0 ||
385 broken(lstrcmpiA(result, bad) == 0),
386 "%s expected '%s', got '%s'\n", key, expected, result);
388 #define okChildString(key, expected) okChildString_(__FILE__, __LINE__, (key), (expected), (expected))
389 #define okChildStringBroken(key, expected, broken) okChildString_(__FILE__, __LINE__, (key), (expected), (broken))
391 static int StrCmpPath(const char* s1, const char* s2)
393 if (!s1 && !s2) return 0;
394 if (!s2) return 1;
395 if (!s1) return -1;
396 while (*s1)
398 if (!*s2)
400 if (*s1=='.')
401 s1++;
402 return (*s1-*s2);
404 if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
406 while (*s1=='/' || *s1=='\\')
407 s1++;
408 while (*s2=='/' || *s2=='\\')
409 s2++;
411 else if (toupper(*s1)==toupper(*s2))
413 s1++;
414 s2++;
416 else
418 return (*s1-*s2);
421 if (*s2=='.')
422 s2++;
423 if (*s2)
424 return -1;
425 return 0;
428 static void okChildPath_(const char* file, int line, const char* key, const char* expected)
430 char* result;
431 int equal, shortequal;
432 result=getChildString("Child", key);
433 if (!result)
435 okShell_(file,line)(FALSE, "%s expected '%s', but key not found or empty\n", key, expected);
436 return;
438 shortequal = FALSE;
439 equal = (StrCmpPath(result, expected) == 0);
440 if (!equal)
442 char altpath[MAX_PATH];
443 DWORD rc = GetLongPathNameA(expected, altpath, sizeof(altpath));
444 if (0 < rc && rc < sizeof(altpath))
445 equal = (StrCmpPath(result, altpath) == 0);
446 if (!equal)
448 rc = GetShortPathNameA(expected, altpath, sizeof(altpath));
449 if (0 < rc && rc < sizeof(altpath))
450 shortequal = (StrCmpPath(result, altpath) == 0);
453 okShell_(file,line)(equal || broken(shortequal) /* XP SP1 */,
454 "%s expected '%s', got '%s'\n", key, expected, result);
456 #define okChildPath(key, expected) okChildPath_(__FILE__, __LINE__, (key), (expected))
458 static void okChildInt_(const char* file, int line, const char* key, int expected)
460 INT result;
461 result=GetPrivateProfileIntA("Child", key, expected, child_file);
462 okShell_(file,line)(result == expected,
463 "%s expected %d, but got %d\n", key, expected, result);
465 #define okChildInt(key, expected) okChildInt_(__FILE__, __LINE__, (key), (expected))
467 static void okChildIntBroken_(const char* file, int line, const char* key, int expected)
469 INT result;
470 result=GetPrivateProfileIntA("Child", key, expected, child_file);
471 okShell_(file,line)(result == expected || broken(result != expected),
472 "%s expected %d, but got %d\n", key, expected, result);
474 #define okChildIntBroken(key, expected) okChildIntBroken_(__FILE__, __LINE__, (key), (expected))
477 /***
479 * ShellExecute wrappers
481 ***/
483 static void strcat_param(char* str, const char* name, const char* param)
485 if (param)
487 if (str[strlen(str)-1] == '"')
488 strcat(str, ", ");
489 strcat(str, name);
490 strcat(str, "=\"");
491 strcat(str, param);
492 strcat(str, "\"");
496 static int _todo_wait = 0;
497 #define todo_wait for (_todo_wait = 1; _todo_wait; _todo_wait = 0)
499 static int bad_shellexecute = 0;
501 static INT_PTR shell_execute_(const char* file, int line, LPCSTR verb, LPCSTR filename, LPCSTR parameters, LPCSTR directory)
503 INT_PTR rc, rcEmpty = 0;
505 if(!verb)
506 rcEmpty = shell_execute_(file, line, "", filename, parameters, directory);
508 strcpy(shell_call, "ShellExecute(");
509 strcat_param(shell_call, "verb", verb);
510 strcat_param(shell_call, "file", filename);
511 strcat_param(shell_call, "params", parameters);
512 strcat_param(shell_call, "dir", directory);
513 strcat(shell_call, ")");
514 strcat(shell_call, assoc_desc);
515 if (winetest_debug > 1)
516 trace_(file, line)("Called %s\n", shell_call);
518 DeleteFileA(child_file);
519 SetLastError(0xcafebabe);
521 /* FIXME: We cannot use ShellExecuteEx() here because if there is no
522 * association it displays the 'Open With' dialog and I could not find
523 * a flag to prevent this.
525 rc=(INT_PTR)ShellExecuteA(NULL, verb, filename, parameters, directory, SW_HIDE);
527 if (rc > 32)
529 int wait_rc;
530 wait_rc=WaitForSingleObject(hEvent, 5000);
531 if (wait_rc == WAIT_TIMEOUT)
533 HWND wnd = FindWindowA("#32770", "Windows");
534 if (!wnd)
535 wnd = FindWindowA("Shell_Flyout", "");
536 if (wnd != NULL)
538 SendMessageA(wnd, WM_CLOSE, 0, 0);
539 win_skip("Skipping shellexecute of file with unassociated extension\n");
540 skip_noassoc_tests = TRUE;
541 rc = SE_ERR_NOASSOC;
544 todo_wine_if(_todo_wait)
545 okShell_(file, line)(wait_rc==WAIT_OBJECT_0 || rc <= 32,
546 "WaitForSingleObject returned %d\n", wait_rc);
548 /* The child process may have changed the result file, so let profile
549 * functions know about it
551 WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
552 if (GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
554 int c;
555 dump_child_(file, line);
556 c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
557 if (c > 0)
558 winetest_add_failures(c);
559 okChildInt_(file, line, "ShlexecVarLE", 0);
560 okChildString_(file, line, "ShlexecVar", "Present", "Present");
563 if(!verb)
565 if (rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */
566 bad_shellexecute = 1;
567 okShell_(file, line)(rc == rcEmpty ||
568 broken(rc != rcEmpty && rcEmpty == SE_ERR_NOASSOC) /* NT4 */,
569 "Got different return value with empty string: %lu %lu\n", rc, rcEmpty);
572 return rc;
574 #define shell_execute(verb, filename, parameters, directory) \
575 shell_execute_(__FILE__, __LINE__, verb, filename, parameters, directory)
577 static INT_PTR shell_execute_ex_(const char* file, int line,
578 DWORD mask, LPCSTR verb, LPCSTR filename,
579 LPCSTR parameters, LPCSTR directory,
580 LPCSTR class)
582 char smask[11];
583 SHELLEXECUTEINFOA sei;
584 BOOL success;
585 INT_PTR rc;
587 /* Add some flags so we can wait for the child process */
588 mask |= SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
590 strcpy(shell_call, "ShellExecuteEx(");
591 sprintf(smask, "0x%x", mask);
592 strcat_param(shell_call, "mask", smask);
593 strcat_param(shell_call, "verb", verb);
594 strcat_param(shell_call, "file", filename);
595 strcat_param(shell_call, "params", parameters);
596 strcat_param(shell_call, "dir", directory);
597 strcat_param(shell_call, "class", class);
598 strcat(shell_call, ")");
599 strcat(shell_call, assoc_desc);
600 if (winetest_debug > 1)
601 trace_(file, line)("Called %s\n", shell_call);
603 sei.cbSize=sizeof(sei);
604 sei.fMask=mask;
605 sei.hwnd=NULL;
606 sei.lpVerb=verb;
607 sei.lpFile=filename;
608 sei.lpParameters=parameters;
609 sei.lpDirectory=directory;
610 sei.nShow=SW_SHOWNORMAL;
611 sei.hInstApp=NULL; /* Out */
612 sei.lpIDList=NULL;
613 sei.lpClass=class;
614 sei.hkeyClass=NULL;
615 sei.dwHotKey=0;
616 U(sei).hIcon=NULL;
617 sei.hProcess=(HANDLE)0xdeadbeef; /* Out */
619 DeleteFileA(child_file);
620 SetLastError(0xcafebabe);
621 success=ShellExecuteExA(&sei);
622 rc=(INT_PTR)sei.hInstApp;
623 okShell_(file, line)((success && rc > 32) || (!success && rc <= 32),
624 "rc=%d and hInstApp=%ld is not allowed\n",
625 success, rc);
627 if (rc > 32)
629 DWORD wait_rc, rc;
630 if (sei.hProcess!=NULL)
632 wait_rc=WaitForSingleObject(sei.hProcess, 5000);
633 okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
634 "WaitForSingleObject(hProcess) returned %d\n",
635 wait_rc);
636 wait_rc = GetExitCodeProcess(sei.hProcess, &rc);
637 okShell_(file, line)(wait_rc, "GetExitCodeProcess() failed le=%u\n", GetLastError());
638 todo_wine_if(_todo_wait)
639 okShell_(file, line)(rc == 0, "child returned %u\n", rc);
640 CloseHandle(sei.hProcess);
642 wait_rc=WaitForSingleObject(hEvent, 5000);
643 todo_wine_if(_todo_wait)
644 okShell_(file, line)(wait_rc==WAIT_OBJECT_0,
645 "WaitForSingleObject returned %d\n", wait_rc);
647 else
648 okShell_(file, line)(sei.hProcess==NULL,
649 "returned a process handle %p\n", sei.hProcess);
651 /* The child process may have changed the result file, so let profile
652 * functions know about it
654 WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
655 if (rc > 32 && GetFileAttributesA(child_file) != INVALID_FILE_ATTRIBUTES)
657 int c;
658 dump_child_(file, line);
659 c = GetPrivateProfileIntA("Child", "Failures", -1, child_file);
660 if (c > 0)
661 winetest_add_failures(c);
662 /* When NOZONECHECKS is specified the environment variables are not
663 * inherited if the process does not have elevated privileges.
665 if ((mask & SEE_MASK_NOZONECHECKS) && skip_shlexec_tests)
667 okChildInt_(file, line, "ShlexecVarLE", 203);
668 okChildString_(file, line, "ShlexecVar", "", "");
670 else
672 okChildInt_(file, line, "ShlexecVarLE", 0);
673 okChildString_(file, line, "ShlexecVar", "Present", "Present");
677 return rc;
679 #define shell_execute_ex(mask, verb, filename, parameters, directory, class) \
680 shell_execute_ex_(__FILE__, __LINE__, mask, verb, filename, parameters, directory, class)
683 /***
685 * Functions to create / delete associations wrappers
687 ***/
689 static BOOL create_test_class(const char* class, BOOL protocol)
691 HKEY hkey, hkey_shell;
692 LONG rc;
694 rc = RegCreateKeyExA(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
695 KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL,
696 &hkey, NULL);
697 ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
698 "could not create class %s (rc=%d)\n", class, rc);
699 if (rc != ERROR_SUCCESS)
700 return FALSE;
702 if (protocol)
704 rc = RegSetValueExA(hkey, "URL Protocol", 0, REG_SZ, (LPBYTE)"", 1);
705 ok(rc == ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
708 rc = RegCreateKeyExA(hkey, "shell", 0, NULL, 0,
709 KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
710 ok(rc == ERROR_SUCCESS, "RegCreateKeyEx 'shell' failed, expected ERROR_SUCCESS, got %d\n", rc);
712 CloseHandle(hkey);
713 CloseHandle(hkey_shell);
714 return TRUE;
717 static BOOL create_test_association(const char* extension)
719 HKEY hkey;
720 char class[MAX_PATH];
721 LONG rc;
723 sprintf(class, "shlexec%s", extension);
724 rc=RegCreateKeyExA(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
725 NULL, &hkey, NULL);
726 ok(rc == ERROR_SUCCESS || rc == ERROR_ACCESS_DENIED,
727 "could not create association %s (rc=%d)\n", class, rc);
728 if (rc != ERROR_SUCCESS)
729 return FALSE;
731 rc=RegSetValueExA(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1);
732 ok(rc==ERROR_SUCCESS, "RegSetValueEx '%s' failed, expected ERROR_SUCCESS, got %d\n", class, rc);
733 CloseHandle(hkey);
735 return create_test_class(class, FALSE);
738 /* Based on RegDeleteTreeW from dlls/advapi32/registry.c */
739 static LSTATUS myRegDeleteTreeA(HKEY hKey, LPCSTR lpszSubKey)
741 LONG ret;
742 DWORD dwMaxSubkeyLen, dwMaxValueLen;
743 DWORD dwMaxLen, dwSize;
744 CHAR szNameBuf[MAX_PATH], *lpszName = szNameBuf;
745 HKEY hSubKey = hKey;
747 if(lpszSubKey)
749 ret = RegOpenKeyExA(hKey, lpszSubKey, 0, KEY_READ, &hSubKey);
750 if (ret) return ret;
753 /* Get highest length for keys, values */
754 ret = RegQueryInfoKeyA(hSubKey, NULL, NULL, NULL, NULL,
755 &dwMaxSubkeyLen, NULL, NULL, &dwMaxValueLen, NULL, NULL, NULL);
756 if (ret) goto cleanup;
758 dwMaxSubkeyLen++;
759 dwMaxValueLen++;
760 dwMaxLen = max(dwMaxSubkeyLen, dwMaxValueLen);
761 if (dwMaxLen > ARRAY_SIZE(szNameBuf))
763 /* Name too big: alloc a buffer for it */
764 if (!(lpszName = heap_alloc(dwMaxLen*sizeof(CHAR))))
766 ret = ERROR_NOT_ENOUGH_MEMORY;
767 goto cleanup;
772 /* Recursively delete all the subkeys */
773 while (TRUE)
775 dwSize = dwMaxLen;
776 if (RegEnumKeyExA(hSubKey, 0, lpszName, &dwSize, NULL,
777 NULL, NULL, NULL)) break;
779 ret = myRegDeleteTreeA(hSubKey, lpszName);
780 if (ret) goto cleanup;
783 if (lpszSubKey)
784 ret = RegDeleteKeyA(hKey, lpszSubKey);
785 else
786 while (TRUE)
788 dwSize = dwMaxLen;
789 if (RegEnumValueA(hKey, 0, lpszName, &dwSize,
790 NULL, NULL, NULL, NULL)) break;
792 ret = RegDeleteValueA(hKey, lpszName);
793 if (ret) goto cleanup;
796 cleanup:
797 /* Free buffer if allocated */
798 if (lpszName != szNameBuf)
799 heap_free(lpszName);
800 if(lpszSubKey)
801 RegCloseKey(hSubKey);
802 return ret;
805 static void delete_test_class(const char* classname)
807 myRegDeleteTreeA(HKEY_CLASSES_ROOT, classname);
810 static void delete_test_association(const char* extension)
812 char classname[MAX_PATH];
814 sprintf(classname, "shlexec%s", extension);
815 delete_test_class(classname);
816 myRegDeleteTreeA(HKEY_CLASSES_ROOT, extension);
819 static void create_test_verb_dde(const char* classname, const char* verb,
820 int rawcmd, const char* cmdtail, const char *ddeexec,
821 const char *application, const char *topic,
822 const char *ifexec)
824 HKEY hkey_shell, hkey_verb, hkey_cmd;
825 char shell[MAX_PATH];
826 char* cmd;
827 LONG rc;
829 strcpy(assoc_desc, " Assoc ");
830 strcat_param(assoc_desc, "class", classname);
831 strcat_param(assoc_desc, "verb", verb);
832 sprintf(shell, "%d", rawcmd);
833 strcat_param(assoc_desc, "rawcmd", shell);
834 strcat_param(assoc_desc, "cmdtail", cmdtail);
835 strcat_param(assoc_desc, "ddeexec", ddeexec);
836 strcat_param(assoc_desc, "app", application);
837 strcat_param(assoc_desc, "topic", topic);
838 strcat_param(assoc_desc, "ifexec", ifexec);
840 sprintf(shell, "%s\\shell", classname);
841 rc=RegOpenKeyExA(HKEY_CLASSES_ROOT, shell, 0,
842 KEY_CREATE_SUB_KEY, &hkey_shell);
843 ok(rc == ERROR_SUCCESS, "%s key creation failed with %d\n", shell, rc);
845 rc=RegCreateKeyExA(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
846 NULL, &hkey_verb, NULL);
847 ok(rc == ERROR_SUCCESS, "%s verb key creation failed with %d\n", verb, rc);
849 rc=RegCreateKeyExA(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
850 NULL, &hkey_cmd, NULL);
851 ok(rc == ERROR_SUCCESS, "\'command\' key creation failed with %d\n", rc);
853 if (rawcmd)
855 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1);
857 else
859 cmd = heap_alloc(strlen(argv0) + 10 + strlen(child_file) + 2 + strlen(cmdtail) + 1);
860 sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
861 rc=RegSetValueExA(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1);
862 ok(rc == ERROR_SUCCESS, "setting command failed with %d\n", rc);
863 heap_free(cmd);
866 if (ddeexec)
868 HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec;
870 rc=RegCreateKeyExA(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE |
871 KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL);
872 ok(rc == ERROR_SUCCESS, "\'ddeexec\' key creation failed with %d\n", rc);
873 rc=RegSetValueExA(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec,
874 strlen(ddeexec)+1);
875 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
877 if (application)
879 rc=RegCreateKeyExA(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE,
880 NULL, &hkey_application, NULL);
881 ok(rc == ERROR_SUCCESS, "\'application\' key creation failed with %d\n", rc);
883 rc=RegSetValueExA(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application,
884 strlen(application)+1);
885 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
886 CloseHandle(hkey_application);
888 if (topic)
890 rc=RegCreateKeyExA(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE,
891 NULL, &hkey_topic, NULL);
892 ok(rc == ERROR_SUCCESS, "\'topic\' key creation failed with %d\n", rc);
893 rc=RegSetValueExA(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic,
894 strlen(topic)+1);
895 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
896 CloseHandle(hkey_topic);
898 if (ifexec)
900 rc=RegCreateKeyExA(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE,
901 NULL, &hkey_ifexec, NULL);
902 ok(rc == ERROR_SUCCESS, "\'ifexec\' key creation failed with %d\n", rc);
903 rc=RegSetValueExA(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec,
904 strlen(ifexec)+1);
905 ok(rc == ERROR_SUCCESS, "set value failed with %d\n", rc);
906 CloseHandle(hkey_ifexec);
908 CloseHandle(hkey_ddeexec);
911 CloseHandle(hkey_shell);
912 CloseHandle(hkey_verb);
913 CloseHandle(hkey_cmd);
916 /* Creates a class' non-DDE test verb.
917 * This function is meant to be used to create long term test verbs and thus
918 * does not trace them.
920 static void create_test_verb(const char* classname, const char* verb,
921 int rawcmd, const char* cmdtail)
923 create_test_verb_dde(classname, verb, rawcmd, cmdtail, NULL, NULL,
924 NULL, NULL);
925 reset_association_description();
929 /***
931 * GetLongPathNameA equivalent that supports Win95 and WinNT
933 ***/
935 static DWORD get_long_path_name(const char* shortpath, char* longpath, DWORD longlen)
937 char tmplongpath[MAX_PATH];
938 const char* p;
939 DWORD sp = 0, lp = 0;
940 DWORD tmplen;
941 WIN32_FIND_DATAA wfd;
942 HANDLE goit;
944 if (!shortpath || !shortpath[0])
945 return 0;
947 if (shortpath[1] == ':')
949 tmplongpath[0] = shortpath[0];
950 tmplongpath[1] = ':';
951 lp = sp = 2;
954 while (shortpath[sp])
956 /* check for path delimiters and reproduce them */
957 if (shortpath[sp] == '\\' || shortpath[sp] == '/')
959 if (!lp || tmplongpath[lp-1] != '\\')
961 /* strip double "\\" */
962 tmplongpath[lp++] = '\\';
964 tmplongpath[lp] = 0; /* terminate string */
965 sp++;
966 continue;
969 p = shortpath + sp;
970 if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
972 tmplongpath[lp++] = *p++;
973 tmplongpath[lp++] = *p++;
975 for (; *p && *p != '/' && *p != '\\'; p++);
976 tmplen = p - (shortpath + sp);
977 lstrcpynA(tmplongpath + lp, shortpath + sp, tmplen + 1);
978 /* Check if the file exists and use the existing file name */
979 goit = FindFirstFileA(tmplongpath, &wfd);
980 if (goit == INVALID_HANDLE_VALUE)
981 return 0;
982 FindClose(goit);
983 strcpy(tmplongpath + lp, wfd.cFileName);
984 lp += strlen(tmplongpath + lp);
985 sp += tmplen;
987 tmplen = strlen(shortpath) - 1;
988 if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
989 (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
990 tmplongpath[lp++] = shortpath[tmplen];
991 tmplongpath[lp] = 0;
993 tmplen = strlen(tmplongpath) + 1;
994 if (tmplen <= longlen)
996 strcpy(longpath, tmplongpath);
997 tmplen--; /* length without 0 */
1000 return tmplen;
1004 /***
1006 * Tests
1008 ***/
1010 static const char* testfiles[]=
1012 "%s\\test file.shlexec",
1013 "%s\\%%nasty%% $file.shlexec",
1014 "%s\\test file.noassoc",
1015 "%s\\test file.noassoc.shlexec",
1016 "%s\\test file.shlexec.noassoc",
1017 "%s\\test_shortcut_shlexec.lnk",
1018 "%s\\test_shortcut_exe.lnk",
1019 "%s\\test file.shl",
1020 "%s\\test file.shlfoo",
1021 "%s\\test file.sha",
1022 "%s\\test file.sfe",
1023 "%s\\test file.shlproto",
1024 "%s\\masked file.shlexec",
1025 "%s\\masked",
1026 "%s\\test file.sde",
1027 "%s\\test file.exe",
1028 "%s\\test2.exe",
1029 "%s\\simple.shlexec",
1030 "%s\\drawback_file.noassoc",
1031 "%s\\drawback_file.noassoc foo.shlexec",
1032 "%s\\drawback_nonexist.noassoc foo.shlexec",
1033 NULL
1036 typedef struct
1038 const char* verb;
1039 const char* basename;
1040 int todo;
1041 INT_PTR rc;
1042 } filename_tests_t;
1044 static filename_tests_t filename_tests[]=
1046 /* Test bad / nonexistent filenames */
1047 {NULL, "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1048 {NULL, "%s\\nonexistent.noassoc", 0x0, SE_ERR_FNF},
1050 /* Standard tests */
1051 {NULL, "%s\\test file.shlexec", 0x0, 33},
1052 {NULL, "%s\\test file.shlexec.", 0x0, 33},
1053 {NULL, "%s\\%%nasty%% $file.shlexec", 0x0, 33},
1054 {NULL, "%s/test file.shlexec", 0x0, 33},
1056 /* Test filenames with no association */
1057 {NULL, "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC},
1059 /* Test double extensions */
1060 {NULL, "%s\\test file.noassoc.shlexec", 0x0, 33},
1061 {NULL, "%s\\test file.shlexec.noassoc", 0x0, SE_ERR_NOASSOC},
1063 /* Test alternate verbs */
1064 {"LowerL", "%s\\nonexistent.shlexec", 0x0, SE_ERR_FNF},
1065 {"LowerL", "%s\\test file.noassoc", 0x0, SE_ERR_NOASSOC},
1067 {"QuotedLowerL", "%s\\test file.shlexec", 0x0, 33},
1068 {"QuotedUpperL", "%s\\test file.shlexec", 0x0, 33},
1070 {"notaverb", "%s\\test file.shlexec", 0x10, SE_ERR_NOASSOC},
1072 {"averb", "%s\\test file.sha", 0x10, 33},
1074 /* Test file masked due to space */
1075 {NULL, "%s\\masked file.shlexec", 0x0, 33},
1076 /* Test if quoting prevents the masking */
1077 {NULL, "%s\\masked file.shlexec", 0x40, 33},
1078 /* Test with incorrect quote */
1079 {NULL, "\"%s\\masked file.shlexec", 0x0, SE_ERR_FNF},
1081 /* Test extension / URI protocol collision */
1082 {NULL, "%s\\test file.shlproto", 0x0, SE_ERR_NOASSOC},
1084 {NULL, NULL, 0}
1087 static filename_tests_t noquotes_tests[]=
1089 /* Test unquoted '%1' thingies */
1090 {"NoQuotes", "%s\\test file.shlexec", 0xa, 33},
1091 {"LowerL", "%s\\test file.shlexec", 0xa, 33},
1092 {"UpperL", "%s\\test file.shlexec", 0xa, 33},
1094 {NULL, NULL, 0}
1097 static void test_lpFile_parsed(void)
1099 char fileA[MAX_PATH];
1100 INT_PTR rc;
1102 if (skip_shlexec_tests)
1104 skip("No filename parsing tests due to lack of .shlexec association\n");
1105 return;
1108 /* existing "drawback_file.noassoc" prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1109 sprintf(fileA, "%s\\drawback_file.noassoc foo.shlexec", tmpdir);
1110 rc=shell_execute(NULL, fileA, NULL, NULL);
1111 okShell(rc > 32, "failed: rc=%lu\n", rc);
1113 /* if quoted, existing "drawback_file.noassoc" not prevents finding "drawback_file.noassoc foo.shlexec" on wine */
1114 sprintf(fileA, "\"%s\\drawback_file.noassoc foo.shlexec\"", tmpdir);
1115 rc=shell_execute(NULL, fileA, NULL, NULL);
1116 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1117 "failed: rc=%lu\n", rc);
1119 /* error should be SE_ERR_FNF, not SE_ERR_NOASSOC */
1120 sprintf(fileA, "\"%s\\drawback_file.noassoc\" foo.shlexec", tmpdir);
1121 rc=shell_execute(NULL, fileA, NULL, NULL);
1122 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1124 /* ""command"" not works on wine (and real win9x and w2k) */
1125 sprintf(fileA, "\"\"%s\\simple.shlexec\"\"", tmpdir);
1126 rc=shell_execute(NULL, fileA, NULL, NULL);
1127 todo_wine okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win9x/2000 */,
1128 "failed: rc=%lu\n", rc);
1130 /* nonexisting "drawback_nonexist.noassoc" not prevents finding "drawback_nonexist.noassoc foo.shlexec" on wine */
1131 sprintf(fileA, "%s\\drawback_nonexist.noassoc foo.shlexec", tmpdir);
1132 rc=shell_execute(NULL, fileA, NULL, NULL);
1133 okShell(rc > 32, "failed: rc=%lu\n", rc);
1135 /* is SEE_MASK_DOENVSUBST default flag? Should only be when XP emulates 9x (XP bug or real 95 or ME behavior ?) */
1136 rc=shell_execute(NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL);
1137 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1139 /* quoted */
1140 rc=shell_execute(NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL);
1141 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
1143 /* test SEE_MASK_DOENVSUBST works */
1144 rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1145 NULL, "%TMPDIR%\\simple.shlexec", NULL, NULL, NULL);
1146 okShell(rc > 32, "failed: rc=%lu\n", rc);
1148 /* quoted lpFile does not work on real win95 and nt4 */
1149 rc=shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
1150 NULL, "\"%TMPDIR%\\simple.shlexec\"", NULL, NULL, NULL);
1151 okShell(rc > 32 || broken(rc == SE_ERR_FNF) /* Win95/NT4 */,
1152 "failed: rc=%lu\n", rc);
1155 typedef struct
1157 const char* cmd;
1158 const char* args[11];
1159 int todo;
1160 } cmdline_tests_t;
1162 static const cmdline_tests_t cmdline_tests[] =
1164 {"exe",
1165 {"exe", NULL}, 0},
1167 {"exe arg1 arg2 \"arg three\" 'four five` six\\ $even)",
1168 {"exe", "arg1", "arg2", "arg three", "'four", "five`", "six\\", "$even)", NULL}, 0},
1170 {"exe arg=1 arg-2 three\tfour\rfour\nfour ",
1171 {"exe", "arg=1", "arg-2", "three", "four\rfour\nfour", NULL}, 0},
1173 {"exe arg\"one\" \"second\"arg thirdarg ",
1174 {"exe", "argone", "secondarg", "thirdarg", NULL}, 0},
1176 /* Don't lose unclosed quoted arguments */
1177 {"exe arg1 \"unclosed",
1178 {"exe", "arg1", "unclosed", NULL}, 0},
1180 {"exe arg1 \"",
1181 {"exe", "arg1", "", NULL}, 0},
1183 /* cmd's metacharacters have no special meaning */
1184 {"exe \"one^\" \"arg\"&two three|four",
1185 {"exe", "one^", "arg&two", "three|four", NULL}, 0},
1187 /* Environment variables are not interpreted either */
1188 {"exe %TMPDIR% %2",
1189 {"exe", "%TMPDIR%", "%2", NULL}, 0},
1191 /* If not followed by a quote, backslashes go through as is */
1192 {"exe o\\ne t\\\\wo t\\\\\\ree f\\\\\\\\our ",
1193 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1195 {"exe \"o\\ne\" \"t\\\\wo\" \"t\\\\\\ree\" \"f\\\\\\\\our\" ",
1196 {"exe", "o\\ne", "t\\\\wo", "t\\\\\\ree", "f\\\\\\\\our", NULL}, 0},
1198 /* When followed by a quote their number is halved and the remainder
1199 * escapes the quote
1201 {"exe \\\"one \\\\\"two\" \\\\\\\"three \\\\\\\\\"four\" end",
1202 {"exe", "\"one", "\\two", "\\\"three", "\\\\four", "end", NULL}, 0},
1204 {"exe \"one\\\" still\" \"two\\\\\" \"three\\\\\\\" still\" \"four\\\\\\\\\" end",
1205 {"exe", "one\" still", "two\\", "three\\\" still", "four\\\\", "end", NULL}, 0},
1207 /* One can put a quote in an unquoted string by tripling it, that is in
1208 * effect quoting it like so """ -> ". The general rule is as follows:
1209 * 3n quotes -> n quotes
1210 * 3n+1 quotes -> n quotes plus start of a quoted string
1211 * 3n+2 quotes -> n quotes (plus an empty string from the remaining pair)
1212 * Nicely, when n is 0 we get the standard rules back.
1214 {"exe two\"\"quotes next",
1215 {"exe", "twoquotes", "next", NULL}, 0},
1217 {"exe three\"\"\"quotes next",
1218 {"exe", "three\"quotes", "next", NULL}, 0},
1220 {"exe four\"\"\"\" quotes\" next 4%3=1",
1221 {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
1223 {"exe five\"\"\"\"\"quotes next",
1224 {"exe", "five\"quotes", "next", NULL}, 0},
1226 {"exe six\"\"\"\"\"\"quotes next",
1227 {"exe", "six\"\"quotes", "next", NULL}, 0},
1229 {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1",
1230 {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
1232 {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next",
1233 {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0},
1235 {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1236 {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1238 /* Inside a quoted string the opening quote is added to the set of
1239 * consecutive quotes to get the effective quotes count. This gives:
1240 * 1+3n quotes -> n quotes
1241 * 1+3n+1 quotes -> n quotes plus closes the quoted string
1242 * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
1244 {"exe \"two\"\"quotes next",
1245 {"exe", "two\"quotes", "next", NULL}, 0},
1247 {"exe \"two\"\" next",
1248 {"exe", "two\"", "next", NULL}, 0},
1250 {"exe \"three\"\"\" quotes\" next 4%3=1",
1251 {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
1253 {"exe \"four\"\"\"\"quotes next",
1254 {"exe", "four\"quotes", "next", NULL}, 0},
1256 {"exe \"five\"\"\"\"\"quotes next",
1257 {"exe", "five\"\"quotes", "next", NULL}, 0},
1259 {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1",
1260 {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
1262 {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next",
1263 {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0},
1265 {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
1266 {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
1268 /* Escaped consecutive quotes are fun */
1269 {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
1270 {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
1272 /* The executable path has its own rules!!!
1273 * - Backslashes have no special meaning.
1274 * - If the first character is a quote, then the second quote ends the
1275 * executable path.
1276 * - The previous rule holds even if the next character is not a space!
1277 * - If the first character is not a quote, then quotes have no special
1278 * meaning either and the executable path stops at the first space.
1279 * - The consecutive quotes rules don't apply either.
1280 * - Even if there is no space between the executable path and the first
1281 * argument, the latter is parsed using the regular rules.
1283 {"exe\"file\"path arg1",
1284 {"exe\"file\"path", "arg1", NULL}, 0},
1286 {"exe\"file\"path\targ1",
1287 {"exe\"file\"path", "arg1", NULL}, 0},
1289 {"exe\"path\\ arg1",
1290 {"exe\"path\\", "arg1", NULL}, 0},
1292 {"\\\"exe \"arg one\"",
1293 {"\\\"exe", "arg one", NULL}, 0},
1295 {"\"spaced exe\" \"next arg\"",
1296 {"spaced exe", "next arg", NULL}, 0},
1298 {"\"spaced exe\"\t\"next arg\"",
1299 {"spaced exe", "next arg", NULL}, 0},
1301 {"\"exe\"arg\" one\" argtwo",
1302 {"exe", "arg one", "argtwo", NULL}, 0},
1304 {"\"spaced exe\\\"arg1 arg2",
1305 {"spaced exe\\", "arg1", "arg2", NULL}, 0},
1307 {"\"two\"\" arg1 ",
1308 {"two", " arg1 ", NULL}, 0},
1310 {"\"three\"\"\" arg2",
1311 {"three", "", "arg2", NULL}, 0},
1313 {"\"four\"\"\"\"arg1",
1314 {"four", "\"arg1", NULL}, 0},
1316 /* If the first character is a space then the executable path is empty */
1317 {" \"arg\"one argtwo",
1318 {"", "argone", "argtwo", NULL}, 0},
1320 {NULL, {NULL}, 0}
1323 static BOOL test_one_cmdline(const cmdline_tests_t* test)
1325 WCHAR cmdW[MAX_PATH], argW[MAX_PATH];
1326 LPWSTR *cl2a;
1327 int cl2a_count;
1328 LPWSTR *argsW;
1329 int i, count;
1331 /* trace("----- cmd='%s'\n", test->cmd); */
1332 MultiByteToWideChar(CP_ACP, 0, test->cmd, -1, cmdW, ARRAY_SIZE(cmdW));
1333 argsW = cl2a = CommandLineToArgvW(cmdW, &cl2a_count);
1334 if (argsW == NULL && cl2a_count == -1)
1336 win_skip("CommandLineToArgvW not implemented, skipping\n");
1337 return FALSE;
1339 ok(!argsW[cl2a_count] || broken(argsW[cl2a_count] != NULL) /* before Vista */,
1340 "expected NULL-terminated list of commandline arguments\n");
1342 count = 0;
1343 while (test->args[count])
1344 count++;
1345 todo_wine_if(test->todo & 0x1)
1346 ok(cl2a_count == count, "%s: expected %d arguments, but got %d\n", test->cmd, count, cl2a_count);
1348 for (i = 0; i < cl2a_count; i++)
1350 if (i < count)
1352 MultiByteToWideChar(CP_ACP, 0, test->args[i], -1, argW, ARRAY_SIZE(argW));
1353 todo_wine_if(test->todo & (1 << (i+4)))
1354 ok(!lstrcmpW(*argsW, argW), "%s: arg[%d] expected %s but got %s\n", test->cmd, i, wine_dbgstr_w(argW), wine_dbgstr_w(*argsW));
1356 else todo_wine_if(test->todo & 0x1)
1357 ok(0, "%s: got extra arg[%d]=%s\n", test->cmd, i, wine_dbgstr_w(*argsW));
1358 argsW++;
1360 LocalFree(cl2a);
1361 return TRUE;
1364 static void test_commandline2argv(void)
1366 static const WCHAR exeW[] = {'e','x','e',0};
1367 const cmdline_tests_t* test;
1368 WCHAR strW[MAX_PATH];
1369 LPWSTR *args;
1370 int numargs;
1371 DWORD le;
1373 test = cmdline_tests;
1374 while (test->cmd)
1376 if (!test_one_cmdline(test))
1377 return;
1378 test++;
1381 SetLastError(0xdeadbeef);
1382 args = CommandLineToArgvW(exeW, NULL);
1383 le = GetLastError();
1384 ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);
1386 SetLastError(0xdeadbeef);
1387 args = CommandLineToArgvW(NULL, NULL);
1388 le = GetLastError();
1389 ok(args == NULL && le == ERROR_INVALID_PARAMETER, "expected NULL with ERROR_INVALID_PARAMETER got %p with %u\n", args, le);
1391 *strW = 0;
1392 args = CommandLineToArgvW(strW, &numargs);
1393 ok(numargs == 1 || broken(numargs > 1), "expected 1 args, got %d\n", numargs);
1394 ok(!args || (!args[numargs] || broken(args[numargs] != NULL) /* before Vista */),
1395 "expected NULL-terminated list of commandline arguments\n");
1396 if (numargs == 1)
1398 GetModuleFileNameW(NULL, strW, ARRAY_SIZE(strW));
1399 ok(!lstrcmpW(args[0], strW), "wrong path to the current executable: %s instead of %s\n", wine_dbgstr_w(args[0]), wine_dbgstr_w(strW));
1401 if (args) LocalFree(args);
1404 /* The goal here is to analyze how ShellExecute() builds the command that
1405 * will be run. The tricky part is that there are three transformation
1406 * steps between the 'parameters' string we pass to ShellExecute() and the
1407 * argument list we observe in the child process:
1408 * - The parsing of 'parameters' string into individual arguments. The tests
1409 * show this is done differently from both CreateProcess() and
1410 * CommandLineToArgv()!
1411 * - The way the command 'formatting directives' such as %1, %2, etc are
1412 * handled.
1413 * - And the way the resulting command line is then parsed to yield the
1414 * argument list we check.
1416 typedef struct
1418 const char* verb;
1419 const char* params;
1420 int todo;
1421 cmdline_tests_t cmd;
1422 cmdline_tests_t broken;
1423 } argify_tests_t;
1425 static const argify_tests_t argify_tests[] =
1427 /* Start with three simple parameters. Notice that one can reorder and
1428 * duplicate the parameters. Also notice how %* take the raw input
1429 * parameters string, including the trailing spaces, no matter what
1430 * arguments have already been used.
1432 {"Params232S", "p2 p3 p4 ", 0xc2,
1433 {" p2 p3 \"p2\" \"p2 p3 p4 \"",
1434 {"", "p2", "p3", "p2", "p2 p3 p4 ", NULL}, 0}},
1436 /* Unquoted argument references like %2 don't automatically quote their
1437 * argument. Similarly, when they are quoted they don't escape the quotes
1438 * that their argument may contain.
1440 {"Params232S", "\"p two\" p3 p4 ", 0x3f3,
1441 {" p two p3 \"p two\" \"\"p two\" p3 p4 \"",
1442 {"", "p", "two", "p3", "p two", "p", "two p3 p4 ", NULL}, 0}},
1444 /* Only single digits are supported so only %1 to %9. Shown here with %20
1445 * because %10 is a pain.
1447 {"Params20", "p", 0,
1448 {" \"p0\"",
1449 {"", "p0", NULL}, 0}},
1451 /* Only (double-)quotes have a special meaning. */
1452 {"Params23456", "'p2 p3` p4\\ $even", 0x40,
1453 {" \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\"",
1454 {"", "'p2", "p3`", "p4\" $even \"", NULL}, 0}},
1456 {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", 0x1c2,
1457 {" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"",
1458 {"", "p=2", "p-3", "p4\tp4\rp4\np4", "", "", NULL}, 0}},
1460 /* In unquoted strings, quotes are treated are a parameter separator just
1461 * like spaces! However they can be doubled to get a literal quote.
1462 * Specifically:
1463 * 2n quotes -> n quotes
1464 * 2n+1 quotes -> n quotes and a parameter separator
1466 {"Params23456789", "one\"quote \"p four\" one\"quote p7", 0xff3,
1467 {" \"one\" \"quote\" \"p four\" \"one\" \"quote\" \"p7\" \"\" \"\"",
1468 {"", "one", "quote", "p four", "one", "quote", "p7", "", "", NULL}, 0}},
1470 {"Params23456789", "two\"\"quotes \"p three\" two\"\"quotes p5", 0xf2,
1471 {" \"two\"quotes\" \"p three\" \"two\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1472 {"", "twoquotes p", "three twoquotes", "p5", "", "", "", "", NULL}, 0}},
1474 {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3,
1475 {" \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\"",
1476 {"", "three\"", "quotes", "p four", "three\"", "quotes", "p6", "", "", NULL}, 0}},
1478 {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", 0xf3,
1479 {" \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1480 {"", "four\"quotes p", "three fourquotes p5 \"", "", "", "", NULL}, 0}},
1482 /* Quoted strings cannot be continued by tacking on a non space character
1483 * either.
1485 {"Params23456", "\"p two\"p3 \"p four\"p5 p6", 0x1f3,
1486 {" \"p two\" \"p3\" \"p four\" \"p5\" \"p6\"",
1487 {"", "p two", "p3", "p four", "p5", "p6", NULL}, 0}},
1489 /* In quoted strings, the quotes are halved and an odd number closes the
1490 * string. Specifically:
1491 * 2n quotes -> n quotes
1492 * 2n+1 quotes -> n quotes and closes the string and hence the parameter
1494 {"Params23456789", "\"one q\"uote \"p four\" \"one q\"uote p7", 0xff3,
1495 {" \"one q\" \"uote\" \"p four\" \"one q\" \"uote\" \"p7\" \"\" \"\"",
1496 {"", "one q", "uote", "p four", "one q", "uote", "p7", "", "", NULL}, 0}},
1498 {"Params23456789", "\"two \"\" quotes\" \"p three\" \"two \"\" quotes\" p5", 0x1ff3,
1499 {" \"two \" quotes\" \"p three\" \"two \" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1500 {"", "two ", "quotes p", "three two", " quotes", "p5", "", "", "", "", NULL}, 0}},
1502 {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", 0xff3,
1503 {" \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\"",
1504 {"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0}},
1506 {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3,
1507 {" \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1508 {"", "four \"", "quotes p", "three four", "", "quotes p5 \"", "", "", "", NULL}, 0}},
1510 /* The quoted string rules also apply to consecutive quotes at the start
1511 * of a parameter but don't count the opening quote!
1513 {"Params23456789", "\"\"twoquotes \"p four\" \"\"twoquotes p7", 0xbf3,
1514 {" \"\" \"twoquotes\" \"p four\" \"\" \"twoquotes\" \"p7\" \"\" \"\"",
1515 {"", "", "twoquotes", "p four", "", "twoquotes", "p7", "", "", NULL}, 0}},
1517 {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3,
1518 {" \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\"",
1519 {"", "three", "quotes p", "three \"three", "quotes p5 \"", "", "", "", NULL}, 0}},
1521 {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", 0xbf3,
1522 {" \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\"",
1523 {"", "\"", "fourquotes", "p four", "\"", "fourquotes", "p7", "", "", NULL}, 0}},
1525 /* An unclosed quoted string gets lost! */
1526 {"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3,
1527 {" \"p2\" \"p3\" \"\" \"\" \"\"",
1528 {"", "p2", "p3", "", "", "", NULL}, 0},
1529 {" \"p2\" \"p3\" \"p3\" \"\" \"\"",
1530 {"", "p2", "p3", "p3", "", "", NULL}, 0}},
1532 /* Backslashes have no special meaning even when preceding quotes. All
1533 * they do is start an unquoted string.
1535 {"Params23456", "\\\"p\\three \"pfour\\\" pfive", 0x73,
1536 {" \"\\\" \"p\\three\" \"pfour\\\" \"pfive\" \"\"",
1537 {"", "\" p\\three pfour\"", "pfive", "", NULL}, 0}},
1539 /* Environment variables are left untouched. */
1540 {"Params23456", "%TMPDIR% %t %c", 0,
1541 {" \"%TMPDIR%\" \"%t\" \"%c\" \"\" \"\"",
1542 {"", "%TMPDIR%", "%t", "%c", "", "", NULL}, 0}},
1544 /* %~2 is equivalent to %*. However %~3 and higher include the spaces
1545 * before the parameter!
1546 * (but not the previous parameter's closing quote fortunately)
1548 {"Params2345Etc", "p2 p3 \"p4\" p5 p6 ", 0x3f3,
1549 {" ~2=\"p2 p3 \"p4\" p5 p6 \" ~3=\" p3 \"p4\" p5 p6 \" ~4=\" \"p4\" p5 p6 \" ~5= p5 p6 ",
1550 {"", "~2=p2 p3 p4 p5 p6 ", "~3= p3 p4 p5 p6 ", "~4= p4 p5 p6 ", "~5=", "p5", "p6", NULL}, 0}},
1552 /* %~n works even if there is no nth parameter. */
1553 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 ", 0x12,
1554 {" ~9=\" \"",
1555 {"", "~9= ", NULL}, 0}},
1557 {"Params9Etc", "p2 p3 p4 p5 p6 p7 ", 0x12,
1558 {" ~9=\"\"",
1559 {"", "~9=", NULL}, 0}},
1561 /* The %~n directives also transmit the tenth parameter and beyond. */
1562 {"Params9Etc", "p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 and beyond!", 0x12,
1563 {" ~9=\" p9 p10 p11 and beyond!\"",
1564 {"", "~9= p9 p10 p11 and beyond!", NULL}, 0}},
1566 /* Bad formatting directives lose their % sign, except those followed by
1567 * a tilde! Environment variables are not expanded but lose their % sign.
1569 {"ParamsBad", "p2 p3 p4 p5", 0x12,
1570 {" \"% - %~ %~0 %~1 %~a %~* a b c TMPDIR\"",
1571 {"", "% - %~ %~0 %~1 %~a %~* a b c TMPDIR", NULL}, 0}},
1573 {NULL, NULL, 0, {NULL, {NULL}, 0}}
1576 static void test_argify(void)
1578 BOOL has_cl2a = TRUE;
1579 char fileA[MAX_PATH], params[2*MAX_PATH+12];
1580 INT_PTR rc;
1581 const argify_tests_t* test;
1582 const cmdline_tests_t *bad;
1583 const char* cmd;
1584 unsigned i, count;
1586 /* Test with a long parameter */
1587 for (rc = 0; rc < MAX_PATH; rc++)
1588 fileA[rc] = 'a' + rc % 26;
1589 fileA[MAX_PATH-1] = '\0';
1590 sprintf(params, "shlexec \"%s\" %s", child_file, fileA);
1592 /* We need NOZONECHECKS on Win2003 to block a dialog */
1593 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL, NULL);
1594 okShell(rc > 32, "failed: rc=%lu\n", rc);
1595 okChildInt("argcA", 4);
1596 okChildPath("argvA3", fileA);
1598 if (skip_shlexec_tests)
1600 skip("No argify tests due to lack of .shlexec association\n");
1601 return;
1604 create_test_verb("shlexec.shlexec", "Params232S", 0, "Params232S %2 %3 \"%2\" \"%*\"");
1605 create_test_verb("shlexec.shlexec", "Params23456", 0, "Params23456 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\"");
1606 create_test_verb("shlexec.shlexec", "Params23456789", 0, "Params23456789 \"%2\" \"%3\" \"%4\" \"%5\" \"%6\" \"%7\" \"%8\" \"%9\"");
1607 create_test_verb("shlexec.shlexec", "Params2345Etc", 0, "Params2345Etc ~2=\"%~2\" ~3=\"%~3\" ~4=\"%~4\" ~5=%~5");
1608 create_test_verb("shlexec.shlexec", "Params9Etc", 0, "Params9Etc ~9=\"%~9\"");
1609 create_test_verb("shlexec.shlexec", "Params20", 0, "Params20 \"%20\"");
1610 create_test_verb("shlexec.shlexec", "ParamsBad", 0, "ParamsBad \"%% %- %~ %~0 %~1 %~a %~* %a %b %c %TMPDIR%\"");
1612 sprintf(fileA, "%s\\test file.shlexec", tmpdir);
1614 test = argify_tests;
1615 while (test->params)
1617 bad = test->broken.cmd ? &test->broken : &test->cmd;
1619 /* trace("***** verb='%s' params='%s'\n", test->verb, test->params); */
1620 rc = shell_execute_ex(SEE_MASK_DOENVSUBST, test->verb, fileA, test->params, NULL, NULL);
1621 okShell(rc > 32, "failed: rc=%lu\n", rc);
1623 count = 0;
1624 while (test->cmd.args[count])
1625 count++;
1626 /* +4 for the shlexec arguments, -1 because of the added ""
1627 * argument for the CommandLineToArgvW() tests.
1629 todo_wine_if(test->todo & 0x1)
1630 okChildInt("argcA", 4 + count - 1);
1632 cmd = getChildString("Child", "cmdlineA");
1633 /* Our commands are such that the verb immediately precedes the
1634 * part we are interested in.
1636 if (cmd) cmd = strstr(cmd, test->verb);
1637 if (cmd) cmd += strlen(test->verb);
1638 if (!cmd) cmd = "(null)";
1639 todo_wine_if(test->todo & 0x2)
1640 okShell(!strcmp(cmd, test->cmd.cmd) || broken(!strcmp(cmd, bad->cmd)),
1641 "the cmdline is '%s' instead of '%s'\n", cmd, test->cmd.cmd);
1643 for (i = 0; i < count - 1; i++)
1645 char argname[18];
1646 sprintf(argname, "argvA%d", 4 + i);
1647 todo_wine_if(test->todo & (1 << (i+4)))
1648 okChildStringBroken(argname, test->cmd.args[i+1], bad->args[i+1]);
1651 if (has_cl2a)
1652 has_cl2a = test_one_cmdline(&(test->cmd));
1653 test++;
1657 static void test_filename(void)
1659 char filename[MAX_PATH];
1660 const filename_tests_t* test;
1661 char* c;
1662 INT_PTR rc;
1664 if (skip_shlexec_tests)
1666 skip("No ShellExecute/filename tests due to lack of .shlexec association\n");
1667 return;
1670 test=filename_tests;
1671 while (test->basename)
1673 BOOL quotedfile = FALSE;
1675 if (skip_noassoc_tests && test->rc == SE_ERR_NOASSOC)
1677 win_skip("Skipping shellexecute of file with unassociated extension\n");
1678 test++;
1679 continue;
1682 sprintf(filename, test->basename, tmpdir);
1683 if (strchr(filename, '/'))
1685 c=filename;
1686 while (*c)
1688 if (*c=='\\')
1689 *c='/';
1690 c++;
1693 if ((test->todo & 0x40)==0)
1695 rc=shell_execute(test->verb, filename, NULL, NULL);
1697 else
1699 char quoted[MAX_PATH + 2];
1701 quotedfile = TRUE;
1702 sprintf(quoted, "\"%s\"", filename);
1703 rc=shell_execute(test->verb, quoted, NULL, NULL);
1705 if (rc > 32)
1706 rc=33;
1707 okShell(rc==test->rc ||
1708 broken(quotedfile && rc == SE_ERR_FNF), /* NT4 */
1709 "failed: rc=%ld err=%u\n", rc, GetLastError());
1710 if (rc == 33)
1712 const char* verb;
1713 todo_wine_if(test->todo & 0x2)
1714 okChildInt("argcA", 5);
1715 verb=(test->verb ? test->verb : "Open");
1716 todo_wine_if(test->todo & 0x4)
1717 okChildString("argvA3", verb);
1718 todo_wine_if(test->todo & 0x8)
1719 okChildPath("argvA4", filename);
1721 test++;
1724 test=noquotes_tests;
1725 while (test->basename)
1727 sprintf(filename, test->basename, tmpdir);
1728 rc=shell_execute(test->verb, filename, NULL, NULL);
1729 if (rc > 32)
1730 rc=33;
1731 todo_wine_if(test->todo & 0x1)
1732 okShell(rc==test->rc, "failed: rc=%ld err=%u\n", rc, GetLastError());
1733 if (rc==0)
1735 int count;
1736 const char* verb;
1737 char* str;
1739 verb=(test->verb ? test->verb : "Open");
1740 todo_wine_if(test->todo & 0x4)
1741 okChildString("argvA3", verb);
1743 count=4;
1744 str=filename;
1745 while (1)
1747 char attrib[18];
1748 char* space;
1749 space=strchr(str, ' ');
1750 if (space)
1751 *space='\0';
1752 sprintf(attrib, "argvA%d", count);
1753 todo_wine_if(test->todo & 0x8)
1754 okChildPath(attrib, str);
1755 count++;
1756 if (!space)
1757 break;
1758 str=space+1;
1760 todo_wine_if(test->todo & 0x2)
1761 okChildInt("argcA", count);
1763 test++;
1766 if (dllver.dwMajorVersion != 0)
1768 /* The more recent versions of shell32.dll accept quoted filenames
1769 * while older ones (e.g. 4.00) don't. Still we want to test this
1770 * because IE 6 depends on the new behavior.
1771 * One day we may need to check the exact version of the dll but for
1772 * now making sure DllGetVersion() is present is sufficient.
1774 sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
1775 rc=shell_execute(NULL, filename, NULL, NULL);
1776 okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1777 okChildInt("argcA", 5);
1778 okChildString("argvA3", "Open");
1779 sprintf(filename, "%s\\test file.shlexec", tmpdir);
1780 okChildPath("argvA4", filename);
1783 sprintf(filename, "\"%s\\test file.sha\"", tmpdir);
1784 rc=shell_execute(NULL, filename, NULL, NULL);
1785 todo_wine okShell(rc > 32, "failed: rc=%ld err=%u\n", rc, GetLastError());
1786 okChildInt("argcA", 5);
1787 todo_wine okChildString("argvA3", "averb");
1788 sprintf(filename, "%s\\test file.sha", tmpdir);
1789 todo_wine okChildPath("argvA4", filename);
1792 typedef struct
1794 const char* urlprefix;
1795 const char* basename;
1796 int flags;
1797 int todo;
1798 } fileurl_tests_t;
1800 #define URL_SUCCESS 0x1
1801 #define USE_COLON 0x2
1802 #define USE_BSLASH 0x4
1804 static fileurl_tests_t fileurl_tests[]=
1806 /* How many slashes does it take... */
1807 {"file:", "%s\\test file.shlexec", URL_SUCCESS, 0},
1808 {"file:/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1809 {"file://", "%s\\test file.shlexec", URL_SUCCESS, 0},
1810 {"file:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1811 {"File:///", "%s\\test file.shlexec", URL_SUCCESS, 0},
1812 {"file:////", "%s\\test file.shlexec", URL_SUCCESS, 0},
1813 {"file://///", "%s\\test file.shlexec", 0, 0},
1815 /* Test with Windows-style paths */
1816 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_COLON, 0},
1817 {"file:///", "%s\\test file.shlexec", URL_SUCCESS | USE_BSLASH, 0},
1819 /* Check handling of hostnames */
1820 {"file://localhost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1821 {"file://localhost:80/", "%s\\test file.shlexec", 0, 0},
1822 {"file://LocalHost/", "%s\\test file.shlexec", URL_SUCCESS, 0},
1823 {"file://127.0.0.1/", "%s\\test file.shlexec", 0, 0},
1824 {"file://::1/", "%s\\test file.shlexec", 0, 0},
1825 {"file://notahost/", "%s\\test file.shlexec", 0, 0},
1827 /* Environment variables are not expanded in URLs */
1828 {"%urlprefix%", "%s\\test file.shlexec", 0, 0x1},
1829 {"file:///", "%%TMPDIR%%\\test file.shlexec", 0, 0},
1831 /* Test shortcuts vs. URLs */
1832 {"file://///", "%s\\test_shortcut_shlexec.lnk", 0, 0x1d},
1834 /* Confuse things by mixing protocols */
1835 {"file://", "shlproto://foo/bar", USE_COLON, 0},
1837 {NULL, NULL, 0, 0}
1840 static void test_fileurls(void)
1842 char filename[MAX_PATH], fileurl[MAX_PATH], longtmpdir[MAX_PATH];
1843 char command[MAX_PATH];
1844 const fileurl_tests_t* test;
1845 char *s;
1846 INT_PTR rc;
1848 if (skip_shlexec_tests)
1850 skip("No file URL tests due to lack of .shlexec association\n");
1851 return;
1854 rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI, NULL,
1855 "file:///nosuchfile.shlexec", NULL, NULL, NULL);
1856 if (rc > 32)
1858 win_skip("shell32 is too old (likely < 4.72). Skipping the file URL tests\n");
1859 return;
1862 get_long_path_name(tmpdir, longtmpdir, ARRAY_SIZE(longtmpdir));
1863 SetEnvironmentVariableA("urlprefix", "file:///");
1865 test=fileurl_tests;
1866 while (test->basename)
1868 /* Build the file URL */
1869 sprintf(filename, test->basename, longtmpdir);
1870 strcpy(fileurl, test->urlprefix);
1871 strcat(fileurl, filename);
1872 s = fileurl + strlen(test->urlprefix);
1873 while (*s)
1875 if (!(test->flags & USE_COLON) && *s == ':')
1876 *s = '|';
1877 else if (!(test->flags & USE_BSLASH) && *s == '\\')
1878 *s = '/';
1879 s++;
1882 /* Test it first with FindExecutable() */
1883 rc = (INT_PTR)FindExecutableA(fileurl, NULL, command);
1884 ok(rc == SE_ERR_FNF, "FindExecutable(%s) failed: bad rc=%lu\n", fileurl, rc);
1886 /* Then ShellExecute() */
1887 if ((test->todo & 0x10) == 0)
1888 rc = shell_execute(NULL, fileurl, NULL, NULL);
1889 else todo_wait
1890 rc = shell_execute(NULL, fileurl, NULL, NULL);
1891 if (bad_shellexecute)
1893 win_skip("shell32 is too old (likely 4.72). Skipping the file URL tests\n");
1894 break;
1896 if (test->flags & URL_SUCCESS)
1898 todo_wine_if(test->todo & 0x1)
1899 okShell(rc > 32, "failed: bad rc=%lu\n", rc);
1901 else
1903 todo_wine_if(test->todo & 0x1)
1904 okShell(rc == SE_ERR_FNF || rc == SE_ERR_PNF ||
1905 broken(rc == SE_ERR_ACCESSDENIED) /* win2000 */,
1906 "failed: bad rc=%lu\n", rc);
1908 if (rc == 33)
1910 todo_wine_if(test->todo & 0x2)
1911 okChildInt("argcA", 5);
1912 todo_wine_if(test->todo & 0x4)
1913 okChildString("argvA3", "Open");
1914 todo_wine_if(test->todo & 0x8)
1915 okChildPath("argvA4", filename);
1917 test++;
1920 SetEnvironmentVariableA("urlprefix", NULL);
1923 static void test_urls(void)
1925 char url[MAX_PATH];
1926 INT_PTR rc;
1928 if (!create_test_class("fakeproto", FALSE))
1930 skip("Unable to create 'fakeproto' class for URL tests\n");
1931 return;
1933 create_test_verb("fakeproto", "open", 0, "URL %1");
1935 create_test_class("shlpaverb", TRUE);
1936 create_test_verb("shlpaverb", "averb", 0, "PAVerb \"%1\"");
1938 /* Protocols must be properly declared */
1939 rc = shell_execute(NULL, "notaproto://foo", NULL, NULL);
1940 ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1941 "%s returned %lu\n", shell_call, rc);
1943 rc = shell_execute(NULL, "fakeproto://foo/bar", NULL, NULL);
1944 todo_wine ok(rc == SE_ERR_NOASSOC || broken(rc == SE_ERR_ACCESSDENIED),
1945 "%s returned %lu\n", shell_call, rc);
1947 /* Here's a real live one */
1948 rc = shell_execute(NULL, "shlproto://foo/bar", NULL, NULL);
1949 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1950 okChildInt("argcA", 5);
1951 okChildString("argvA3", "URL");
1952 okChildString("argvA4", "shlproto://foo/bar");
1954 /* Check default verb detection */
1955 rc = shell_execute(NULL, "shlpaverb://foo/bar", NULL, NULL);
1956 todo_wine ok(rc > 32 || /* XP+IE7 - Win10 */
1957 broken(rc == SE_ERR_NOASSOC), /* XP+IE6 */
1958 "%s failed: rc=%lu\n", shell_call, rc);
1959 if (rc > 32)
1961 okChildInt("argcA", 5);
1962 todo_wine okChildString("argvA3", "PAVerb");
1963 todo_wine okChildString("argvA4", "shlpaverb://foo/bar");
1966 /* But alternative verbs are a recent feature! */
1967 rc = shell_execute("averb", "shlproto://foo/bar", NULL, NULL);
1968 ok(rc > 32 || /* Win8 - Win10 */
1969 broken(rc == SE_ERR_ACCESSDENIED), /* XP - Win7 */
1970 "%s failed: rc=%lu\n", shell_call, rc);
1971 if (rc > 32)
1973 okChildString("argvA3", "AVerb");
1974 okChildString("argvA4", "shlproto://foo/bar");
1977 /* A .lnk ending does not turn a URL into a shortcut */
1978 todo_wait rc = shell_execute(NULL, "shlproto://foo/bar.lnk", NULL, NULL);
1979 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1980 okChildInt("argcA", 5);
1981 todo_wine okChildString("argvA3", "URL");
1982 todo_wine okChildString("argvA4", "shlproto://foo/bar.lnk");
1984 /* Neither does a .exe extension */
1985 rc = shell_execute(NULL, "shlproto://foo/bar.exe", NULL, NULL);
1986 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1987 okChildInt("argcA", 5);
1988 okChildString("argvA3", "URL");
1989 okChildString("argvA4", "shlproto://foo/bar.exe");
1991 /* But a class name overrides it */
1992 rc = shell_execute(NULL, "shlproto://foo/bar", "shlexec.shlexec", NULL);
1993 ok(rc > 32, "%s failed: rc=%lu\n", shell_call, rc);
1994 okChildInt("argcA", 5);
1995 okChildString("argvA3", "URL");
1996 okChildString("argvA4", "shlproto://foo/bar");
1998 /* Environment variables are expanded in URLs (but not in file URLs!) */
1999 rc = shell_execute_ex(SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI,
2000 NULL, "shlproto://%TMPDIR%/bar", NULL, NULL, NULL);
2001 okShell(rc > 32, "failed: rc=%lu\n", rc);
2002 okChildInt("argcA", 5);
2003 sprintf(url, "shlproto://%s/bar", tmpdir);
2004 okChildString("argvA3", "URL");
2005 okChildStringBroken("argvA4", url, "shlproto://%TMPDIR%/bar");
2007 /* But only after the path has been identified as a URL */
2008 SetEnvironmentVariableA("urlprefix", "shlproto:///");
2009 rc = shell_execute(NULL, "%urlprefix%foo", NULL, NULL);
2010 todo_wine ok(rc == SE_ERR_FNF, "%s returned %lu\n", shell_call, rc);
2011 SetEnvironmentVariableA("urlprefix", NULL);
2013 delete_test_class("fakeproto");
2014 delete_test_class("shlpaverb");
2017 static void test_find_executable(void)
2019 char notepad_path[MAX_PATH];
2020 char filename[MAX_PATH];
2021 char command[MAX_PATH];
2022 const filename_tests_t* test;
2023 INT_PTR rc;
2025 if (!create_test_association(".sfe"))
2027 skip("Unable to create association for '.sfe'\n");
2028 return;
2030 create_test_verb("shlexec.sfe", "Open", 1, "%1");
2032 /* Don't test FindExecutable(..., NULL), it always crashes */
2034 strcpy(command, "your word");
2035 if (0) /* Can crash on Vista! */
2037 rc=(INT_PTR)FindExecutableA(NULL, NULL, command);
2038 ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2039 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2042 GetSystemDirectoryA( notepad_path, MAX_PATH );
2043 strcat( notepad_path, "\\notepad.exe" );
2045 /* Search for something that should be in the system-wide search path (no default directory) */
2046 strcpy(command, "your word");
2047 rc=(INT_PTR)FindExecutableA("notepad.exe", NULL, command);
2048 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2049 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2051 /* Search for something that should be in the system-wide search path (with default directory) */
2052 strcpy(command, "your word");
2053 rc=(INT_PTR)FindExecutableA("notepad.exe", tmpdir, command);
2054 ok(rc > 32, "FindExecutable(%s) returned %ld\n", "notepad.exe", rc);
2055 ok(strcasecmp(command, notepad_path) == 0, "FindExecutable(%s) returned command=[%s]\n", "notepad.exe", command);
2057 strcpy(command, "your word");
2058 rc=(INT_PTR)FindExecutableA(tmpdir, NULL, command);
2059 ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %ld\n", rc);
2060 ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command);
2062 sprintf(filename, "%s\\test file.sfe", tmpdir);
2063 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2064 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2065 /* Depending on the platform, command could be '%1' or 'test file.sfe' */
2067 rc=(INT_PTR)FindExecutableA("test file.sfe", tmpdir, command);
2068 ok(rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2070 rc=(INT_PTR)FindExecutableA("test file.sfe", NULL, command);
2071 ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %ld\n", filename, rc);
2073 delete_test_association(".sfe");
2075 if (!create_test_association(".shl"))
2077 skip("Unable to create association for '.shl'\n");
2078 return;
2080 create_test_verb("shlexec.shl", "Open", 0, "Open");
2082 sprintf(filename, "%s\\test file.shl", tmpdir);
2083 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2084 ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %ld\n", filename, rc);
2086 sprintf(filename, "%s\\test file.shlfoo", tmpdir);
2087 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2089 delete_test_association(".shl");
2091 if (rc > 32)
2093 /* On Windows XP and 2003 FindExecutable() is completely broken.
2094 * Probably what it does is convert the filename to 8.3 format,
2095 * which as a side effect converts the '.shlfoo' extension to '.shl',
2096 * and then tries to find an association for '.shl'. This means it
2097 * will normally fail on most extensions with more than 3 characters,
2098 * like '.mpeg', etc.
2099 * Also it means we cannot do any other test.
2101 win_skip("FindExecutable() is broken -> not running 4+ character extension tests\n");
2102 return;
2105 if (skip_shlexec_tests)
2107 skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2108 return;
2111 test=filename_tests;
2112 while (test->basename)
2114 sprintf(filename, test->basename, tmpdir);
2115 if (strchr(filename, '/'))
2117 char* c;
2118 c=filename;
2119 while (*c)
2121 if (*c=='\\')
2122 *c='/';
2123 c++;
2126 /* Win98 does not '\0'-terminate command! */
2127 memset(command, '\0', sizeof(command));
2128 rc=(INT_PTR)FindExecutableA(filename, NULL, command);
2129 if (rc > 32)
2130 rc=33;
2131 todo_wine_if(test->todo & 0x10)
2132 ok(rc==test->rc, "FindExecutable(%s) failed: rc=%ld\n", filename, rc);
2133 if (rc > 32)
2135 BOOL equal;
2136 equal=strcmp(command, argv0) == 0 ||
2137 /* NT4 returns an extra 0x8 character! */
2138 (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0);
2139 todo_wine_if(test->todo & 0x20)
2140 ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n",
2141 filename, command, argv0);
2143 test++;
2148 static filename_tests_t lnk_tests[]=
2150 /* Pass bad / nonexistent filenames as a parameter */
2151 {NULL, "%s\\nonexistent.shlexec", 0xa, 33},
2152 {NULL, "%s\\nonexistent.noassoc", 0xa, 33},
2154 /* Pass regular paths as a parameter */
2155 {NULL, "%s\\test file.shlexec", 0xa, 33},
2156 {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33},
2158 /* Pass filenames with no association as a parameter */
2159 {NULL, "%s\\test file.noassoc", 0xa, 33},
2161 {NULL, NULL, 0}
2164 static void test_lnks(void)
2166 char filename[MAX_PATH];
2167 char params[MAX_PATH];
2168 const filename_tests_t* test;
2169 INT_PTR rc;
2171 if (skip_shlexec_tests)
2172 skip("No FindExecutable/filename tests due to lack of .shlexec association\n");
2173 else
2175 /* Should open through our association */
2176 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2177 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2178 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2179 okChildInt("argcA", 5);
2180 okChildString("argvA3", "Open");
2181 sprintf(params, "%s\\test file.shlexec", tmpdir);
2182 get_long_path_name(params, filename, sizeof(filename));
2183 okChildPath("argvA4", filename);
2185 todo_wait rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_DOENVSUBST, NULL, "%TMPDIR%\\test_shortcut_shlexec.lnk", NULL, NULL, NULL);
2186 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2187 okChildInt("argcA", 5);
2188 todo_wine okChildString("argvA3", "Open");
2189 sprintf(params, "%s\\test file.shlexec", tmpdir);
2190 get_long_path_name(params, filename, sizeof(filename));
2191 todo_wine okChildPath("argvA4", filename);
2194 /* Should just run our executable */
2195 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2196 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2197 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2198 okChildInt("argcA", 4);
2199 okChildString("argvA3", "Lnk");
2201 if (!skip_shlexec_tests)
2203 /* An explicit class overrides lnk's ContextMenuHandler */
2204 rc=shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, "shlexec.shlexec");
2205 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2206 okChildInt("argcA", 5);
2207 okChildString("argvA3", "Open");
2208 okChildPath("argvA4", filename);
2211 if (dllver.dwMajorVersion>=6)
2213 char* c;
2214 /* Recent versions of shell32.dll accept '/'s in shortcut paths.
2215 * Older versions don't or are quite buggy in this regard.
2217 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2218 c=filename;
2219 while (*c)
2221 if (*c=='\\')
2222 *c='/';
2223 c++;
2225 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL, NULL);
2226 okShell(rc > 32, "failed: rc=%lu err=%u\n", rc, GetLastError());
2227 okChildInt("argcA", 4);
2228 okChildString("argvA3", "Lnk");
2231 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2232 test=lnk_tests;
2233 while (test->basename)
2235 params[0]='\"';
2236 sprintf(params+1, test->basename, tmpdir);
2237 strcat(params,"\"");
2238 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
2239 NULL, NULL);
2240 if (rc > 32)
2241 rc=33;
2242 todo_wine_if(test->todo & 0x1)
2243 okShell(rc==test->rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2244 if (rc==0)
2246 todo_wine_if(test->todo & 0x2)
2247 okChildInt("argcA", 5);
2248 todo_wine_if(test->todo & 0x4)
2249 okChildString("argvA3", "Lnk");
2250 sprintf(params, test->basename, tmpdir);
2251 okChildPath("argvA4", params);
2253 test++;
2258 static void test_exes(void)
2260 char filename[MAX_PATH];
2261 char params[1024];
2262 INT_PTR rc;
2264 sprintf(params, "shlexec \"%s\" Exec", child_file);
2266 /* We need NOZONECHECKS on Win2003 to block a dialog */
2267 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
2268 NULL, NULL);
2269 okShell(rc > 32, "returned %lu\n", rc);
2270 okChildInt("argcA", 4);
2271 okChildString("argvA3", "Exec");
2273 if (! skip_noassoc_tests)
2275 sprintf(filename, "%s\\test file.noassoc", tmpdir);
2276 if (CopyFileA(argv0, filename, FALSE))
2278 rc=shell_execute(NULL, filename, params, NULL);
2279 todo_wine {
2280 okShell(rc==SE_ERR_NOASSOC, "returned %lu\n", rc);
2284 else
2286 win_skip("Skipping shellexecute of file with unassociated extension\n");
2289 /* test combining executable and parameters */
2290 sprintf(filename, "%s shlexec \"%s\" Exec", argv0, child_file);
2291 rc = shell_execute(NULL, filename, NULL, NULL);
2292 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2294 sprintf(filename, "\"%s\" shlexec \"%s\" Exec", argv0, child_file);
2295 rc = shell_execute(NULL, filename, NULL, NULL);
2296 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2298 /* A verb, even if invalid, overrides the normal handling of executables */
2299 todo_wait rc = shell_execute_ex(SEE_MASK_FLAG_NO_UI,
2300 "notaverb", argv0, NULL, NULL, NULL);
2301 todo_wine okShell(rc == SE_ERR_NOASSOC, "returned %lu\n", rc);
2303 if (!skip_shlexec_tests)
2305 /* A class overrides the normal handling of executables too */
2306 /* FIXME SEE_MASK_FLAG_NO_UI is only needed due to Wine's bug */
2307 rc = shell_execute_ex(SEE_MASK_CLASSNAME | SEE_MASK_FLAG_NO_UI,
2308 NULL, argv0, NULL, NULL, ".shlexec");
2309 todo_wine okShell(rc > 32, "returned %lu\n", rc);
2310 okChildInt("argcA", 5);
2311 todo_wine okChildString("argvA3", "Open");
2312 todo_wine okChildPath("argvA4", argv0);
2316 typedef struct
2318 const char* command;
2319 const char* ddeexec;
2320 const char* application;
2321 const char* topic;
2322 const char* ifexec;
2323 int expectedArgs;
2324 const char* expectedDdeExec;
2325 BOOL broken;
2326 } dde_tests_t;
2328 static dde_tests_t dde_tests[] =
2330 /* Test passing and not passing command-line
2331 * argument, no DDE */
2332 {"", NULL, NULL, NULL, NULL, 0, ""},
2333 {"\"%1\"", NULL, NULL, NULL, NULL, 1, ""},
2335 /* Test passing and not passing command-line
2336 * argument, with DDE */
2337 {"", "[open(\"%1\")]", "shlexec", "dde", NULL, 0, "[open(\"%s\")]"},
2338 {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, 1, "[open(\"%s\")]"},
2340 /* Test unquoted %1 in command and ddeexec
2341 * (test filename has space) */
2342 {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", TRUE /* before vista */},
2344 /* Test ifexec precedence over ddeexec */
2345 {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", 0, "[ifexec(\"%s\")]"},
2347 /* Test default DDE topic */
2348 {"", "[open(\"%1\")]", "shlexec", NULL, NULL, 0, "[open(\"%s\")]"},
2350 /* Test default DDE application */
2351 {"", "[open(\"%1\")]", NULL, "dde", NULL, 0, "[open(\"%s\")]"},
2353 {NULL}
2356 static int waitforinputidle_count;
2357 static DWORD WINAPI hooked_WaitForInputIdle(HANDLE process, DWORD timeout)
2359 waitforinputidle_count++;
2360 if (winetest_debug > 1)
2361 trace("WaitForInputIdle() waiting for dde event timeout=min(%u,5s)\n", timeout);
2362 timeout = timeout < 5000 ? timeout : 5000;
2363 return WaitForSingleObject(dde_ready_event, timeout);
2367 * WaitForInputIdle() will normally return immediately for console apps. That's
2368 * a problem for us because ShellExecute will assume that an app is ready to
2369 * receive DDE messages after it has called WaitForInputIdle() on that app.
2370 * To work around that we install our own version of WaitForInputIdle() that
2371 * will wait for the child to explicitly tell us that it is ready. We do that
2372 * by changing the entry for WaitForInputIdle() in the shell32 import address
2373 * table.
2375 static void hook_WaitForInputIdle(DWORD (WINAPI *new_func)(HANDLE, DWORD))
2377 char *base;
2378 PIMAGE_NT_HEADERS nt_headers;
2379 DWORD import_directory_rva;
2380 PIMAGE_IMPORT_DESCRIPTOR import_descriptor;
2381 int hook_count = 0;
2383 base = (char *) GetModuleHandleA("shell32.dll");
2384 nt_headers = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER) base)->e_lfanew);
2385 import_directory_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
2387 /* Search for the correct imported module by walking the import descriptors */
2388 import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(base + import_directory_rva);
2389 while (U(*import_descriptor).OriginalFirstThunk != 0)
2391 char *import_module_name;
2393 import_module_name = base + import_descriptor->Name;
2394 if (lstrcmpiA(import_module_name, "user32.dll") == 0 ||
2395 lstrcmpiA(import_module_name, "user32") == 0)
2397 PIMAGE_THUNK_DATA int_entry;
2398 PIMAGE_THUNK_DATA iat_entry;
2400 /* The import name table and import address table are two parallel
2401 * arrays. We need the import name table to find the imported
2402 * routine and the import address table to patch the address, so
2403 * walk them side by side */
2404 int_entry = (PIMAGE_THUNK_DATA)(base + U(*import_descriptor).OriginalFirstThunk);
2405 iat_entry = (PIMAGE_THUNK_DATA)(base + import_descriptor->FirstThunk);
2406 while (int_entry->u1.Ordinal != 0)
2408 if (! IMAGE_SNAP_BY_ORDINAL(int_entry->u1.Ordinal))
2410 PIMAGE_IMPORT_BY_NAME import_by_name;
2411 import_by_name = (PIMAGE_IMPORT_BY_NAME)(base + int_entry->u1.AddressOfData);
2412 if (lstrcmpA((char *) import_by_name->Name, "WaitForInputIdle") == 0)
2414 /* Found the correct routine in the correct imported module. Patch it. */
2415 DWORD old_prot;
2416 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &old_prot);
2417 iat_entry->u1.Function = (ULONG_PTR) new_func;
2418 VirtualProtect(&iat_entry->u1.Function, sizeof(ULONG_PTR), old_prot, &old_prot);
2419 if (winetest_debug > 1)
2420 trace("Hooked %s.WaitForInputIdle\n", import_module_name);
2421 hook_count++;
2422 break;
2425 int_entry++;
2426 iat_entry++;
2428 break;
2431 import_descriptor++;
2433 ok(hook_count, "Could not hook WaitForInputIdle()\n");
2436 static void test_dde(void)
2438 char filename[MAX_PATH], defApplication[MAX_PATH];
2439 const dde_tests_t* test;
2440 char params[1024];
2441 INT_PTR rc;
2442 HANDLE map;
2443 char *shared_block;
2444 DWORD ddeflags;
2446 hook_WaitForInputIdle(hooked_WaitForInputIdle);
2448 sprintf(filename, "%s\\test file.sde", tmpdir);
2450 /* Default service is application name minus path and extension */
2451 strcpy(defApplication, strrchr(argv0, '\\')+1);
2452 *strchr(defApplication, '.') = 0;
2454 map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
2455 4096, "winetest_shlexec_dde_map");
2456 shared_block = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 4096);
2458 ddeflags = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
2459 test = dde_tests;
2460 while (test->command)
2462 if (!create_test_association(".sde"))
2464 skip("Unable to create association for '.sde'\n");
2465 return;
2467 create_test_verb_dde("shlexec.sde", "Open", 0, test->command, test->ddeexec,
2468 test->application, test->topic, test->ifexec);
2470 if (test->application != NULL || test->topic != NULL)
2472 strcpy(shared_block, test->application ? test->application : defApplication);
2473 strcpy(shared_block + strlen(shared_block) + 1, test->topic ? test->topic : SZDDESYS_TOPIC);
2475 else
2477 shared_block[0] = '\0';
2478 shared_block[1] = '\0';
2480 ddeExec[0] = 0;
2482 waitforinputidle_count = 0;
2483 dde_ready_event = CreateEventA(NULL, TRUE, FALSE, "winetest_shlexec_dde_ready");
2484 rc = shell_execute_ex(ddeflags, NULL, filename, NULL, NULL, NULL);
2485 CloseHandle(dde_ready_event);
2486 if (!(ddeflags & SEE_MASK_WAITFORINPUTIDLE) && rc == SE_ERR_DDEFAIL &&
2487 GetLastError() == ERROR_FILE_NOT_FOUND &&
2488 strcmp(winetest_platform, "windows") == 0)
2490 /* Windows 10 does not call WaitForInputIdle() for DDE which creates
2491 * a race condition as the DDE server may not have time to start up.
2492 * When that happens the test fails with the above results and we
2493 * compensate by forcing the WaitForInputIdle() call.
2495 trace("Adding SEE_MASK_WAITFORINPUTIDLE for Windows 10\n");
2496 ddeflags |= SEE_MASK_WAITFORINPUTIDLE;
2497 delete_test_association(".sde");
2498 Sleep(CHILD_DDE_TIMEOUT);
2499 continue;
2501 okShell(32 < rc, "failed: rc=%lu err=%u\n", rc, GetLastError());
2502 if (test->ddeexec)
2503 okShell(waitforinputidle_count == 1 ||
2504 broken(waitforinputidle_count == 0) /* Win10 race */,
2505 "WaitForInputIdle() was called %u times\n",
2506 waitforinputidle_count);
2507 else
2508 okShell(waitforinputidle_count == 0, "WaitForInputIdle() was called %u times for a non-DDE case\n", waitforinputidle_count);
2510 if (32 < rc)
2512 if (test->broken)
2513 okChildIntBroken("argcA", test->expectedArgs + 3);
2514 else
2515 okChildInt("argcA", test->expectedArgs + 3);
2517 if (test->expectedArgs == 1) okChildPath("argvA3", filename);
2519 sprintf(params, test->expectedDdeExec, filename);
2520 okChildPath("ddeExec", params);
2522 reset_association_description();
2524 delete_test_association(".sde");
2525 test++;
2528 UnmapViewOfFile(shared_block);
2529 CloseHandle(map);
2530 hook_WaitForInputIdle((void *) WaitForInputIdle);
2533 #define DDE_DEFAULT_APP_VARIANTS 3
2534 typedef struct
2536 const char* command;
2537 const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS];
2538 int todo;
2539 int rc[DDE_DEFAULT_APP_VARIANTS];
2540 } dde_default_app_tests_t;
2542 static dde_default_app_tests_t dde_default_app_tests[] =
2544 /* There are three possible sets of results: Windows <= 2000, XP SP1 and
2545 * >= XP SP2. Use the first two tests to determine which results to expect.
2548 /* Test unquoted existing filename with a space */
2549 {"%s\\test file.exe", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2550 {"%s\\test2 file.exe", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2552 /* Test unquoted existing filename with a space */
2553 {"%s\\test file.exe param", {"test file", "test file", "test"}, 0x0, {33, 33, 33}},
2555 /* Test quoted existing filename with a space */
2556 {"\"%s\\test file.exe\"", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2557 {"\"%s\\test file.exe\" param", {"test file", "test file", "test file"}, 0x0, {33, 33, 33}},
2559 /* Test unquoted filename with a space that doesn't exist, but
2560 * test2.exe does */
2561 {"%s\\test2 file.exe param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2563 /* Test quoted filename with a space that does not exist */
2564 {"\"%s\\test2 file.exe\"", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2565 {"\"%s\\test2 file.exe\" param", {"", "", "test2 file"}, 0x0, {5, 2, 33}},
2567 /* Test filename supplied without the extension */
2568 {"%s\\test2", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2569 {"%s\\test2 param", {"test2", "", "test2"}, 0x0, {33, 5, 33}},
2571 /* Test an unquoted nonexistent filename */
2572 {"%s\\notexist.exe", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2573 {"%s\\notexist.exe param", {"", "", "notexist"}, 0x0, {5, 2, 33}},
2575 /* Test an application that will be found on the path */
2576 {"cmd", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2577 {"cmd param", {"cmd", "cmd", "cmd"}, 0x0, {33, 33, 33}},
2579 /* Test an application that will not be found on the path */
2580 {"xyzwxyzwxyz", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2581 {"xyzwxyzwxyz param", {"", "", "xyzwxyzwxyz"}, 0x0, {5, 2, 33}},
2583 {NULL, {NULL}, 0, {0}}
2586 typedef struct
2588 char *filename;
2589 DWORD threadIdParent;
2590 } dde_thread_info_t;
2592 static DWORD CALLBACK ddeThread(LPVOID arg)
2594 dde_thread_info_t *info = arg;
2595 assert(info && info->filename);
2596 PostThreadMessageA(info->threadIdParent,
2597 WM_QUIT,
2598 shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL, NULL),
2600 ExitThread(0);
2603 static void test_dde_default_app(void)
2605 char filename[MAX_PATH];
2606 HSZ hszApplication;
2607 dde_thread_info_t info = { filename, GetCurrentThreadId() };
2608 const dde_default_app_tests_t* test;
2609 char params[1024];
2610 DWORD threadId;
2611 MSG msg;
2612 INT_PTR rc;
2613 int which = 0;
2614 HDDEDATA ret;
2615 BOOL b;
2617 post_quit_on_execute = FALSE;
2618 ddeInst = 0;
2619 rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES |
2620 CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0);
2621 ok(rc == DMLERR_NO_ERROR, "got %lx\n", rc);
2623 sprintf(filename, "%s\\test file.sde", tmpdir);
2625 /* It is strictly not necessary to register an application name here, but wine's
2626 * DdeNameService implementation complains if 0 is passed instead of
2627 * hszApplication with DNS_FILTEROFF */
2628 hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2629 hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI);
2630 ok(hszApplication && hszTopic, "got %p and %p\n", hszApplication, hszTopic);
2631 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_REGISTER | DNS_FILTEROFF);
2632 ok(ret != 0, "got %p\n", ret);
2634 test = dde_default_app_tests;
2635 while (test->command)
2637 HANDLE thread;
2639 if (!create_test_association(".sde"))
2641 skip("Unable to create association for '.sde'\n");
2642 return;
2644 sprintf(params, test->command, tmpdir);
2645 create_test_verb_dde("shlexec.sde", "Open", 1, params, "[test]", NULL,
2646 "shlexec", NULL);
2647 ddeApplication[0] = 0;
2649 /* No application will be run as we will respond to the first DDE event,
2650 * so don't wait for it */
2651 SetEvent(hEvent);
2653 thread = CreateThread(NULL, 0, ddeThread, &info, 0, &threadId);
2654 ok(thread != NULL, "got %p\n", thread);
2655 while (GetMessageA(&msg, NULL, 0, 0)) DispatchMessageA(&msg);
2656 rc = msg.wParam > 32 ? 33 : msg.wParam;
2658 /* The first two tests determine which set of results to expect.
2659 * First check the platform as only the first set of results is
2660 * acceptable for Wine.
2662 if (strcmp(winetest_platform, "wine"))
2664 if (test == dde_default_app_tests)
2666 if (strcmp(ddeApplication, test->expectedDdeApplication[0]))
2667 which = 2;
2669 else if (test == dde_default_app_tests + 1)
2671 if (which == 0 && rc == test->rc[1])
2672 which = 1;
2673 trace("DDE result variant %d\n", which);
2677 todo_wine_if(test->todo & 0x1)
2678 okShell(rc==test->rc[which], "failed: rc=%lu err=%u\n",
2679 rc, GetLastError());
2680 if (rc == 33)
2682 todo_wine_if(test->todo & 0x2)
2683 ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]),
2684 "Expected application '%s', got '%s'\n",
2685 test->expectedDdeApplication[which], ddeApplication);
2687 reset_association_description();
2689 delete_test_association(".sde");
2690 test++;
2693 ret = DdeNameService(ddeInst, hszApplication, 0, DNS_UNREGISTER);
2694 ok(ret != 0, "got %p\n", ret);
2695 b = DdeFreeStringHandle(ddeInst, hszTopic);
2696 ok(b, "got %d\n", b);
2697 b = DdeFreeStringHandle(ddeInst, hszApplication);
2698 ok(b, "got %d\n", b);
2699 b = DdeUninitialize(ddeInst);
2700 ok(b, "got %d\n", b);
2703 static void init_test(void)
2705 HMODULE hdll;
2706 HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
2707 char filename[MAX_PATH];
2708 WCHAR lnkfile[MAX_PATH];
2709 char params[1024];
2710 const char* const * testfile;
2711 lnk_desc_t desc;
2712 DWORD rc;
2713 HRESULT r;
2715 hdll=GetModuleHandleA("shell32.dll");
2716 pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
2717 if (pDllGetVersion)
2719 dllver.cbSize=sizeof(dllver);
2720 pDllGetVersion(&dllver);
2721 trace("major=%d minor=%d build=%d platform=%d\n",
2722 dllver.dwMajorVersion, dllver.dwMinorVersion,
2723 dllver.dwBuildNumber, dllver.dwPlatformID);
2725 else
2727 memset(&dllver, 0, sizeof(dllver));
2730 r = CoInitialize(NULL);
2731 ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
2732 if (FAILED(r))
2733 exit(1);
2735 rc=GetModuleFileNameA(NULL, argv0, sizeof(argv0));
2736 ok(rc != 0 && rc < sizeof(argv0), "got %d\n", rc);
2737 if (GetFileAttributesA(argv0)==INVALID_FILE_ATTRIBUTES)
2739 strcat(argv0, ".so");
2740 ok(GetFileAttributesA(argv0)!=INVALID_FILE_ATTRIBUTES,
2741 "unable to find argv0!\n");
2744 /* Older versions (win 2k) fail tests if there is a space in
2745 the path. */
2746 if (dllver.dwMajorVersion <= 5)
2747 strcpy(filename, "c:\\");
2748 else
2749 GetTempPathA(sizeof(filename), filename);
2750 GetTempFileNameA(filename, "wt", 0, tmpdir);
2751 GetLongPathNameA(tmpdir, tmpdir, sizeof(tmpdir));
2752 DeleteFileA( tmpdir );
2753 rc = CreateDirectoryA( tmpdir, NULL );
2754 ok( rc, "failed to create %s err %u\n", tmpdir, GetLastError() );
2755 /* Set %TMPDIR% for the tests */
2756 SetEnvironmentVariableA("TMPDIR", tmpdir);
2758 rc = GetTempFileNameA(tmpdir, "wt", 0, child_file);
2759 ok(rc != 0, "got %d\n", rc);
2760 init_event(child_file);
2762 /* Set up the test files */
2763 testfile=testfiles;
2764 while (*testfile)
2766 HANDLE hfile;
2768 sprintf(filename, *testfile, tmpdir);
2769 hfile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
2770 FILE_ATTRIBUTE_NORMAL, NULL);
2771 if (hfile==INVALID_HANDLE_VALUE)
2773 trace("unable to create '%s': err=%u\n", filename, GetLastError());
2774 assert(0);
2776 CloseHandle(hfile);
2777 testfile++;
2780 /* Setup the test shortcuts */
2781 sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
2782 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile));
2783 desc.description=NULL;
2784 desc.workdir=NULL;
2785 sprintf(filename, "%s\\test file.shlexec", tmpdir);
2786 desc.path=filename;
2787 desc.pidl=NULL;
2788 desc.arguments="ignored";
2789 desc.showcmd=0;
2790 desc.icon=NULL;
2791 desc.icon_id=0;
2792 desc.hotkey=0;
2793 create_lnk(lnkfile, &desc, 0);
2795 sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
2796 MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, ARRAY_SIZE(lnkfile));
2797 desc.description=NULL;
2798 desc.workdir=NULL;
2799 desc.path=argv0;
2800 desc.pidl=NULL;
2801 sprintf(params, "shlexec \"%s\" Lnk", child_file);
2802 desc.arguments=params;
2803 desc.showcmd=0;
2804 desc.icon=NULL;
2805 desc.icon_id=0;
2806 desc.hotkey=0;
2807 create_lnk(lnkfile, &desc, 0);
2809 /* Create a basic association suitable for most tests */
2810 if (!create_test_association(".shlexec"))
2812 skip_shlexec_tests = TRUE;
2813 skip("Unable to create association for '.shlexec'\n");
2814 return;
2816 create_test_verb("shlexec.shlexec", "Open", 0, "Open \"%1\"");
2817 create_test_verb("shlexec.shlexec", "NoQuotes", 0, "NoQuotes %1");
2818 create_test_verb("shlexec.shlexec", "LowerL", 0, "LowerL %l");
2819 create_test_verb("shlexec.shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\"");
2820 create_test_verb("shlexec.shlexec", "UpperL", 0, "UpperL %L");
2821 create_test_verb("shlexec.shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\"");
2823 create_test_association(".sha");
2824 create_test_verb("shlexec.sha", "averb", 0, "AVerb \"%1\"");
2826 create_test_class("shlproto", TRUE);
2827 create_test_verb("shlproto", "open", 0, "URL \"%1\"");
2828 create_test_verb("shlproto", "averb", 0, "AVerb \"%1\"");
2830 /* Set an environment variable to see if it is inherited */
2831 SetEnvironmentVariableA("ShlexecVar", "Present");
2834 static void cleanup_test(void)
2836 char filename[MAX_PATH];
2837 const char* const * testfile;
2839 /* Delete the test files */
2840 testfile=testfiles;
2841 while (*testfile)
2843 sprintf(filename, *testfile, tmpdir);
2844 /* Make sure we can delete the files ('test file.noassoc' is read-only now) */
2845 SetFileAttributesA(filename, FILE_ATTRIBUTE_NORMAL);
2846 DeleteFileA(filename);
2847 testfile++;
2849 DeleteFileA(child_file);
2850 RemoveDirectoryA(tmpdir);
2852 /* Delete the test association */
2853 delete_test_association(".shlexec");
2854 delete_test_association(".sha");
2855 delete_test_class("shlproto");
2857 CloseHandle(hEvent);
2859 CoUninitialize();
2862 static void test_directory(void)
2864 char path[MAX_PATH], curdir[MAX_PATH];
2865 char params[1024], dirpath[1024];
2866 INT_PTR rc;
2868 sprintf(path, "%s\\test2.exe", tmpdir);
2869 CopyFileA(argv0, path, FALSE);
2871 sprintf(params, "shlexec \"%s\" Exec", child_file);
2873 /* Test with the current directory */
2874 GetCurrentDirectoryA(sizeof(curdir), curdir);
2875 SetCurrentDirectoryA(tmpdir);
2876 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2877 NULL, "test2.exe", params, NULL, NULL);
2878 okShell(rc > 32, "returned %lu\n", rc);
2879 okChildInt("argcA", 4);
2880 okChildString("argvA3", "Exec");
2881 todo_wine okChildPath("longPath", path);
2882 SetCurrentDirectoryA(curdir);
2884 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2885 NULL, "test2.exe", params, NULL, NULL);
2886 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2888 /* Explicitly specify the directory to use */
2889 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2890 NULL, "test2.exe", params, tmpdir, NULL);
2891 okShell(rc > 32, "returned %lu\n", rc);
2892 okChildInt("argcA", 4);
2893 okChildString("argvA3", "Exec");
2894 todo_wine okChildPath("longPath", path);
2896 /* Specify it through an environment variable */
2897 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2898 NULL, "test2.exe", params, "%TMPDIR%", NULL);
2899 todo_wine okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2901 rc=shell_execute_ex(SEE_MASK_DOENVSUBST|SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2902 NULL, "test2.exe", params, "%TMPDIR%", NULL);
2903 okShell(rc > 32, "returned %lu\n", rc);
2904 okChildInt("argcA", 4);
2905 okChildString("argvA3", "Exec");
2906 todo_wine okChildPath("longPath", path);
2908 /* Not a colon-separated directory list */
2909 sprintf(dirpath, "%s:%s", curdir, tmpdir);
2910 rc=shell_execute_ex(SEE_MASK_NOZONECHECKS|SEE_MASK_FLAG_NO_UI,
2911 NULL, "test2.exe", params, dirpath, NULL);
2912 okShell(rc == SE_ERR_FNF, "returned %lu\n", rc);
2915 START_TEST(shlexec)
2918 myARGC = winetest_get_mainargs(&myARGV);
2919 if (myARGC >= 3)
2921 doChild(myARGC, myARGV);
2922 /* Skip the tests/failures trace for child processes */
2923 ExitProcess(winetest_get_failures());
2926 init_test();
2928 test_commandline2argv();
2929 test_argify();
2930 test_lpFile_parsed();
2931 test_filename();
2932 test_fileurls();
2933 test_urls();
2934 test_find_executable();
2935 test_lnks();
2936 test_exes();
2937 test_dde();
2938 test_dde_default_app();
2939 test_directory();
2941 cleanup_test();