Test "%l" and "%L". At the same time this tests alternate verbs.
[wine] / dlls / shell32 / tests / shlexec.c
1 /*
2  * Unit test of the ShellExecute function.
3  *
4  * Copyright 2005 Francois Gouget for CodeWeavers
5  *
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.
10  *
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.
15  *
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
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  * - we may want to test ShellExecuteEx() instead of ShellExecute()
30  *   and then we could also check its return value
31  * - ShellExecuteEx() also calls SetLastError() with meaningful values which
32  *   we could check
33  */
34
35 #include <stdio.h>
36 #include <assert.h>
37
38 #include "wtypes.h"
39 #include "winbase.h"
40 #include "windef.h"
41 #include "shellapi.h"
42 #include "shlwapi.h"
43 #include "wine/test.h"
44
45 #include "shell32_test.h"
46
47
48 static char argv0[MAX_PATH];
49 static int myARGC;
50 static char** myARGV;
51 static char tmpdir[MAX_PATH];
52 static char child_file[MAX_PATH];
53 static DLLVERSIONINFO dllver;
54
55
56 /***
57  *
58  * ShellExecute wrappers
59  *
60  ***/
61 static void dump_child();
62
63 static HANDLE hEvent;
64 static void init_event(const char* child_file)
65 {
66     char* event_name;
67     event_name=strrchr(child_file, '\\')+1;
68     hEvent=CreateEvent(NULL, FALSE, FALSE, event_name);
69 }
70
71 static void strcat_param(char* str, const char* param)
72 {
73     if (param!=NULL)
74     {
75         strcat(str, "\"");
76         strcat(str, param);
77         strcat(str, "\"");
78     }
79     else
80     {
81         strcat(str, "null");
82     }
83 }
84
85 static char shell_call[2048]="";
86 static int shell_execute(LPCSTR operation, LPCSTR file, LPCSTR parameters, LPCSTR directory)
87 {
88     int rc;
89
90     strcpy(shell_call, "ShellExecute(");
91     strcat_param(shell_call, operation);
92     strcat(shell_call, ", ");
93     strcat_param(shell_call, file);
94     strcat(shell_call, ", ");
95     strcat_param(shell_call, parameters);
96     strcat(shell_call, ", ");
97     strcat_param(shell_call, directory);
98     strcat(shell_call, ")");
99     if (winetest_debug > 1)
100         trace("%s\n", shell_call);
101
102     DeleteFile(child_file);
103     SetLastError(0xcafebabe);
104
105     /* FIXME: We cannot use ShellExecuteEx() here because if there is no
106      * association it displays the 'Open With' dialog and I could not find
107      * a flag to prevent this.
108      */
109     rc=(int)ShellExecute(NULL, operation, file, parameters, directory,
110                          SW_SHOWNORMAL);
111
112     if (rc>=32)
113     {
114         int wait_rc;
115         wait_rc=WaitForSingleObject(hEvent, 60000);
116         ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait_rc);
117     }
118     /* The child process may have changed the result file, so let profile
119      * functions know about it
120      */
121     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
122     if (rc>=32)
123         dump_child();
124
125     return rc;
126 }
127
128 static int shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file,
129                             LPCSTR parameters, LPCSTR directory)
130 {
131     SHELLEXECUTEINFO sei;
132     BOOL success;
133     int rc;
134
135     strcpy(shell_call, "ShellExecuteEx(");
136     strcat_param(shell_call, operation);
137     strcat(shell_call, ", ");
138     strcat_param(shell_call, file);
139     strcat(shell_call, ", ");
140     strcat_param(shell_call, parameters);
141     strcat(shell_call, ", ");
142     strcat_param(shell_call, directory);
143     strcat(shell_call, ")");
144     if (winetest_debug > 1)
145         trace("%s\n", shell_call);
146
147     sei.cbSize=sizeof(sei);
148     sei.fMask=SEE_MASK_NOCLOSEPROCESS | mask;
149     sei.hwnd=NULL;
150     sei.lpVerb=operation;
151     sei.lpFile=file;
152     sei.lpParameters=parameters;
153     sei.lpDirectory=directory;
154     sei.nShow=SW_SHOWNORMAL;
155     sei.hInstApp=NULL; /* Out */
156     sei.lpIDList=NULL;
157     sei.lpClass=NULL;
158     sei.hkeyClass=NULL;
159     sei.dwHotKey=0;
160     U(sei).hIcon=NULL;
161     sei.hProcess=NULL; /* Out */
162
163     DeleteFile(child_file);
164     SetLastError(0xcafebabe);
165     success=ShellExecuteEx(&sei);
166     rc=(int)sei.hInstApp;
167     ok((success && rc >= 32) || (!success && rc < 32),
168        "%s rc=%d and hInstApp=%d is not allowed\n", shell_call, success, rc);
169
170     if (rc>=32)
171     {
172         int wait_rc;
173         if (sei.hProcess!=NULL)
174         {
175             wait_rc=WaitForSingleObject(sei.hProcess, 60000);
176             ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject(hProcess) returned %d\n", wait_rc);
177         }
178         wait_rc=WaitForSingleObject(hEvent, 60000);
179         ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait_rc);
180     }
181     /* The child process may have changed the result file, so let profile
182      * functions know about it
183      */
184     WritePrivateProfileStringA(NULL, NULL, NULL, child_file);
185     if (rc>=32)
186         dump_child();
187
188     return rc;
189 }
190
191
192
193 /***
194  *
195  * Functions to create / delete associations wrappers
196  *
197  ***/
198
199 static void create_test_association(const char* extension)
200 {
201     HKEY hkey, hkey_shell;
202     char class[MAX_PATH];
203     LONG rc;
204
205     sprintf(class, "shlexec%s", extension);
206     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE,
207                       NULL, &hkey, NULL);
208     assert(rc==ERROR_SUCCESS);
209     rc=RegSetValueEx(hkey, NULL, 0, REG_SZ, class, strlen(class)+1);
210     assert(rc==ERROR_SUCCESS);
211     CloseHandle(hkey);
212
213     rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, class, 0, NULL, 0,
214                       KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS, NULL, &hkey, NULL);
215     assert(rc==ERROR_SUCCESS);
216     rc=RegCreateKeyEx(hkey, "shell", 0, NULL, 0,
217                       KEY_CREATE_SUB_KEY, NULL, &hkey_shell, NULL);
218     assert(rc==ERROR_SUCCESS);
219     CloseHandle(hkey);
220     CloseHandle(hkey_shell);
221 }
222
223 static void delete_test_association(const char* extension)
224 {
225     char class[MAX_PATH];
226
227     sprintf(class, "shlexec%s", extension);
228     SHDeleteKey(HKEY_CLASSES_ROOT, class);
229     SHDeleteKey(HKEY_CLASSES_ROOT, extension);
230 }
231
232 static void create_test_verb(const char* extension, const char* verb,
233                              const char* cmdtail)
234 {
235     HKEY hkey_shell, hkey_verb, hkey_cmd;
236     char shell[MAX_PATH];
237     char* cmd;
238     LONG rc;
239
240     sprintf(shell, "shlexec%s\\shell", extension);
241     rc=RegOpenKeyEx(HKEY_CLASSES_ROOT, shell, 0,
242                     KEY_CREATE_SUB_KEY, &hkey_shell);
243     assert(rc==ERROR_SUCCESS);
244     rc=RegCreateKeyEx(hkey_shell, verb, 0, NULL, 0, KEY_CREATE_SUB_KEY,
245                       NULL, &hkey_verb, NULL);
246     assert(rc==ERROR_SUCCESS);
247     rc=RegCreateKeyEx(hkey_verb, "command", 0, NULL, 0, KEY_SET_VALUE,
248                       NULL, &hkey_cmd, NULL);
249     assert(rc==ERROR_SUCCESS);
250
251     cmd=malloc(strlen(argv0)+10+strlen(child_file)+2+strlen(cmdtail)+1);
252     sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail);
253     rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, cmd, strlen(cmd)+1);
254     assert(rc==ERROR_SUCCESS);
255
256     free(cmd);
257     CloseHandle(hkey_shell);
258     CloseHandle(hkey_verb);
259     CloseHandle(hkey_cmd);
260 }
261
262
263
264 /***
265  *
266  * Functions to check that the child process was started just right
267  * (borrowed from dlls/kernel32/tests/process.c)
268  *
269  ***/
270
271 static const char* encodeA(const char* str)
272 {
273     static char encoded[2*1024+1];
274     char*       ptr;
275     size_t      len,i;
276
277     if (!str) return "";
278     len = strlen(str) + 1;
279     if (len >= sizeof(encoded)/2)
280     {
281         fprintf(stderr, "string is too long!\n");
282         assert(0);
283     }
284     ptr = encoded;
285     for (i = 0; i < len; i++)
286         sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]);
287     ptr[2 * len] = '\0';
288     return ptr;
289 }
290
291 static unsigned decode_char(char c)
292 {
293     if (c >= '0' && c <= '9') return c - '0';
294     if (c >= 'a' && c <= 'f') return c - 'a' + 10;
295     assert(c >= 'A' && c <= 'F');
296     return c - 'A' + 10;
297 }
298
299 static char* decodeA(const char* str)
300 {
301     static char decoded[1024];
302     char*       ptr;
303     size_t      len,i;
304
305     len = strlen(str) / 2;
306     if (!len--) return NULL;
307     if (len >= sizeof(decoded))
308     {
309         fprintf(stderr, "string is too long!\n");
310         assert(0);
311     }
312     ptr = decoded;
313     for (i = 0; i < len; i++)
314         ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]);
315     ptr[len] = '\0';
316     return ptr;
317 }
318
319 static void     childPrintf(HANDLE h, const char* fmt, ...)
320 {
321     va_list     valist;
322     char        buffer[1024];
323     DWORD       w;
324
325     va_start(valist, fmt);
326     vsprintf(buffer, fmt, valist);
327     va_end(valist);
328     WriteFile(h, buffer, strlen(buffer), &w, NULL);
329 }
330
331 static void doChild(int argc, char** argv)
332 {
333     char* filename;
334     HANDLE hFile;
335     int i;
336
337     filename=argv[2];
338     hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
339     if (hFile == INVALID_HANDLE_VALUE)
340         return;
341
342     /* Arguments */
343     childPrintf(hFile, "[Arguments]\n");
344     if (winetest_debug > 2)
345         trace("argcA=%d\n", argc);
346     childPrintf(hFile, "argcA=%d\n", argc);
347     for (i = 0; i < argc; i++)
348     {
349         if (winetest_debug > 2)
350             trace("argvA%d=%s\n", i, argv[i]);
351         childPrintf(hFile, "argvA%d=%s\n", i, encodeA(argv[i]));
352     }
353     CloseHandle(hFile);
354
355     init_event(filename);
356     SetEvent(hEvent);
357     CloseHandle(hEvent);
358 }
359
360 static char* getChildString(const char* sect, const char* key)
361 {
362     char        buf[1024];
363     char*       ret;
364
365     GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file);
366     if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL;
367     assert(!(strlen(buf) & 1));
368     ret = decodeA(buf);
369     return ret;
370 }
371
372 static void dump_child()
373 {
374     if (winetest_debug > 1)
375     {
376         char key[18];
377         char* str;
378         int i, c;
379
380         c=GetPrivateProfileIntA("Arguments", "argcA", -1, child_file);
381         trace("argcA=%d\n",c);
382         for (i=0;i<c;i++)
383         {
384             sprintf(key, "argvA%d", i);
385             str=getChildString("Arguments", key);
386             trace("%s=%s\n", key, str);
387         }
388     }
389 }
390
391 static int StrCmpPath(const char* s1, const char* s2)
392 {
393     if (!s1 && !s2) return 0;
394     if (!s2) return 1;
395     if (!s1) return -1;
396     while (*s1)
397     {
398         if (!*s2)
399         {
400             if (*s1=='.')
401                 s1++;
402             return (*s1-*s2);
403         }
404         if ((*s1=='/' || *s1=='\\') && (*s2=='/' || *s2=='\\'))
405         {
406             while (*s1=='/' || *s1=='\\')
407                 s1++;
408             while (*s2=='/' || *s2=='\\')
409                 s2++;
410         }
411         else if (toupper(*s1)==toupper(*s2))
412         {
413             s1++;
414             s2++;
415         }
416         else
417         {
418             return (*s1-*s2);
419         }
420     }
421     if (*s2=='.')
422         s2++;
423     if (*s2)
424         return -1;
425     return 0;
426 }
427
428 static int _okChildString(const char* file, int line, const char* key, const char* expected)
429 {
430     char* result;
431     result=getChildString("Arguments", key);
432     return ok_(file, line)(lstrcmpiA(result, expected) == 0,
433                "%s expected '%s', got '%s'\n", key, expected, result);
434 }
435
436 static int _okChildPath(const char* file, int line, const char* key, const char* expected)
437 {
438     char* result;
439     result=getChildString("Arguments", key);
440     return ok_(file, line)(StrCmpPath(result, expected) == 0,
441                "%s expected '%s', got '%s'\n", key, expected, result);
442 }
443
444 static int _okChildInt(const char* file, int line, const char* key, int expected)
445 {
446     INT result;
447     result=GetPrivateProfileIntA("Arguments", key, expected, child_file);
448     return ok_(file, line)(result == expected,
449                "%s expected %d, but got %d\n", key, expected, result);
450 }
451
452 #define okChildString(key, expected) _okChildString(__FILE__, __LINE__, (key), (expected))
453 #define okChildPath(key, expected) _okChildPath(__FILE__, __LINE__, (key), (expected))
454 #define okChildInt(key, expected)    _okChildInt(__FILE__, __LINE__, (key), (expected))
455
456
457
458 /***
459  *
460  * Tests
461  *
462  ***/
463
464 static const char* testfiles[]=
465 {
466     "%s\\test file.shlexec",
467     "%s\\%%nasty%% $file.shlexec",
468     "%s\\test file.noassoc",
469     "%s\\test file.noassoc.shlexec",
470     "%s\\test file.shlexec.noassoc",
471     "%s\\test_shortcut_shlexec.lnk",
472     "%s\\test_shortcut_exe.lnk",
473     NULL
474 };
475
476 typedef struct
477 {
478     char* verb;
479     char* basename;
480     int rc;
481     int todo;
482 } filename_tests_t;
483
484 static filename_tests_t filename_tests[]=
485 {
486     /* Test bad / nonexistent filenames */
487     {NULL,           "%s\\nonexistent.shlexec", ERROR_FILE_NOT_FOUND, 0x1},
488     {NULL,           "%s\\nonexistent.noassoc", ERROR_FILE_NOT_FOUND, 0x1},
489
490     /* Standard tests */
491     {NULL,           "%s\\test file.shlexec",   0, 0x0},
492     {NULL,           "%s\\test file.shlexec.",  0, 0x0},
493     {NULL,           "%s\\%%nasty%% $file.shlexec", 0, 0x0},
494     {NULL,           "%s/test file.shlexec",    0, 0x0},
495
496     /* Test filenames with no association */
497     {NULL,           "%s\\test file.noassoc",   SE_ERR_NOASSOC, 0x0},
498
499     /* Test double extensions */
500     {NULL,           "%s\\test file.noassoc.shlexec", 0, 0},
501     {NULL,           "%s\\test file.shlexec.noassoc", SE_ERR_NOASSOC, 0x0},
502
503     /* Test alternate verbs */
504     {"LowerL",       "%s\\nonexistent.shlexec", ERROR_FILE_NOT_FOUND, 0x1},
505     {"LowerL",       "%s\\test file.noassoc",   SE_ERR_NOASSOC, 0x0},
506
507     {"QuotedLowerL", "%s\\test file.shlexec",   0, 0x0},
508     {"QuotedUpperL", "%s\\test file.shlexec",   0, 0x0},
509
510     {NULL, NULL, 0, 0}
511 };
512
513 static filename_tests_t noquotes_tests[]=
514 {
515     /* Test unquoted '%1' thingies */
516     {"NoQuotes",     "%s\\test file.shlexec",   0, 0xa},
517     {"LowerL",       "%s\\test file.shlexec",   0, 0x0},
518     {"UpperL",       "%s\\test file.shlexec",   0, 0x0},
519
520     {NULL, NULL, 0, 0}
521 };
522
523 static void test_filename()
524 {
525     char filename[MAX_PATH];
526     const filename_tests_t* test;
527     char* c;
528     int rc;
529
530     test=filename_tests;
531     while (test->basename)
532     {
533         sprintf(filename, test->basename, tmpdir);
534         if (strchr(filename, '/'))
535         {
536             c=filename;
537             while (*c)
538             {
539                 if (*c=='\\')
540                     *c='/';
541                 c++;
542             }
543         }
544         rc=shell_execute(test->verb, filename, NULL, NULL);
545         if (rc>=32)
546             rc=0;
547         if ((test->todo & 0x1)==0)
548         {
549             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
550                rc, GetLastError());
551         }
552         else todo_wine
553         {
554             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
555                rc, GetLastError());
556         }
557         if (rc==0)
558         {
559             const char* verb;
560             if ((test->todo & 0x2)==0)
561             {
562                 okChildInt("argcA", 5);
563             }
564             else todo_wine
565             {
566                 okChildInt("argcA", 5);
567             }
568             verb=(test->verb ? test->verb : "Open");
569             if ((test->todo & 0x4)==0)
570             {
571                 okChildString("argvA3", verb);
572             }
573             else todo_wine
574             {
575                 okChildString("argvA3", verb);
576             }
577             if ((test->todo & 0x8)==0)
578             {
579                 okChildPath("argvA4", filename);
580             }
581             else todo_wine
582             {
583                 okChildPath("argvA4", filename);
584             }
585         }
586         test++;
587     }
588
589     test=noquotes_tests;
590     while (test->basename)
591     {
592         sprintf(filename, test->basename, tmpdir);
593         rc=shell_execute(test->verb, filename, NULL, NULL);
594         if (rc>=32)
595             rc=0;
596         if ((test->todo & 0x1)==0)
597         {
598             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
599                rc, GetLastError());
600         }
601         else todo_wine
602         {
603             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
604                rc, GetLastError());
605         }
606         if (rc==0)
607         {
608             int count;
609             const char* verb;
610             char* str;
611
612             verb=(test->verb ? test->verb : "Open");
613             if ((test->todo & 0x4)==0)
614             {
615                 okChildString("argvA3", verb);
616             }
617             else todo_wine
618             {
619                 okChildString("argvA3", verb);
620             }
621
622             count=4;
623             str=filename;
624             while (1)
625             {
626                 char attrib[18];
627                 char* space;
628                 space=strchr(str, ' ');
629                 if (space)
630                     *space='\0';
631                 sprintf(attrib, "argvA%d", count);
632                 if ((test->todo & 0x8)==0)
633                 {
634                     okChildPath(attrib, str);
635                 }
636                 else todo_wine
637                 {
638                     okChildPath(attrib, str);
639                 }
640                 count++;
641                 if (!space)
642                     break;
643                 str=space+1;
644             }
645             if ((test->todo & 0x2)==0)
646             {
647                 okChildInt("argcA", count);
648             }
649             else todo_wine
650             {
651                 okChildInt("argcA", count);
652             }
653         }
654         test++;
655     }
656
657     if (dllver.dwMajorVersion != 0)
658     {
659         /* The more recent versions of shell32.dll accept quoted filenames
660          * while older ones (e.g. 4.00) don't. Still we want to test this
661          * because IE 6 depends on the new behavior.
662          * One day we may need to check the exact version of the dll but for
663          * now making sure DllGetVersion() is present is sufficient.
664          */
665         sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir);
666         rc=shell_execute(NULL, filename, NULL, NULL);
667         ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
668            GetLastError());
669         okChildInt("argcA", 5);
670         okChildString("argvA3", "Open");
671         sprintf(filename, "%s\\test file.shlexec", tmpdir);
672         okChildPath("argvA4", filename);
673     }
674 }
675
676
677 static filename_tests_t lnk_tests[]=
678 {
679     /* Pass bad / nonexistent filenames as a parameter */
680     {NULL, "%s\\nonexistent.shlexec", 0, 0xa},
681     {NULL, "%s\\nonexistent.noassoc", 0, 0xa},
682
683     /* Pass regular paths as a parameter */
684     {NULL, "%s\\test file.shlexec",   0, 0xa},
685     {NULL, "%s/%%nasty%% $file.shlexec", 0, 0xa},
686
687     /* Pass filenames with no association as a parameter */
688     {NULL, "%s\\test file.noassoc",   0, 0xa},
689
690     {NULL, NULL, 0, 0}
691 };
692
693 static void test_lnks()
694 {
695     char filename[MAX_PATH];
696     char params[MAX_PATH];
697     const filename_tests_t* test;
698     int rc;
699
700     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
701     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
702     ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
703        GetLastError());
704     okChildInt("argcA", 5);
705     okChildString("argvA3", "Open");
706     sprintf(filename, "%s\\test file.shlexec", tmpdir);
707     todo_wine {
708     okChildPath("argvA4", filename);
709     }
710
711     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
712     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
713     ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
714        GetLastError());
715     okChildInt("argcA", 4);
716     okChildString("argvA3", "Lnk");
717
718     if (dllver.dwMajorVersion>=6)
719     {
720         char* c;
721        /* Recent versions of shell32.dll accept '/'s in shortcut paths.
722          * Older versions don't or are quite buggy in this regard.
723          */
724         sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
725         c=filename;
726         while (*c)
727         {
728             if (*c=='\\')
729                 *c='/';
730             c++;
731         }
732         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL);
733         ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc,
734            GetLastError());
735         okChildInt("argcA", 4);
736         okChildString("argvA3", "Lnk");
737     }
738
739     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
740     test=lnk_tests;
741     while (test->basename)
742     {
743         params[0]='\"';
744         sprintf(params+1, test->basename, tmpdir);
745         strcat(params,"\"");
746         rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params,
747                             NULL);
748         if (rc>=32)
749             rc=0;
750         if ((test->todo & 0x1)==0)
751         {
752             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
753                rc, GetLastError());
754         }
755         else todo_wine
756         {
757             ok(rc==test->rc, "%s failed: rc=%d err=%ld\n", shell_call,
758                rc, GetLastError());
759         }
760         if (rc==0)
761         {
762             if ((test->todo & 0x2)==0)
763             {
764                 okChildInt("argcA", 5);
765             }
766             else todo_wine
767             {
768                 okChildInt("argcA", 5);
769             }
770             if ((test->todo & 0x4)==0)
771             {
772                 okChildString("argvA3", "Lnk");
773             }
774             else todo_wine
775             {
776                 okChildString("argvA3", "Lnk");
777             }
778             sprintf(params, test->basename, tmpdir);
779             if ((test->todo & 0x8)==0)
780             {
781                 okChildPath("argvA4", params);
782             }
783             else todo_wine
784             {
785                 okChildPath("argvA4", params);
786             }
787         }
788         test++;
789     }
790 }
791
792
793 static void test_exes()
794 {
795     char filename[MAX_PATH];
796     char params[1024];
797     int rc;
798
799     sprintf(params, "shlexec \"%s\" Exec", child_file);
800
801     /* We need NOZONECHECKS on Win2003 to block a dialog */
802     rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params,
803                         NULL);
804     ok(rc>=32, "%s returned %d\n", shell_call, rc);
805     okChildInt("argcA", 4);
806     okChildString("argvA3", "Exec");
807
808     sprintf(filename, "%s\\test file.noassoc", tmpdir);
809     if (CopyFile(argv0, filename, FALSE))
810     {
811         rc=shell_execute(NULL, filename, params, NULL);
812         todo_wine {
813         ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%d\n", shell_call, rc);
814         }
815     }
816 }
817
818
819 static void init_test()
820 {
821     HMODULE hdll;
822     HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*);
823     char filename[MAX_PATH];
824     WCHAR lnkfile[MAX_PATH];
825     char params[1024];
826     const char* const * testfile;
827     lnk_desc_t desc;
828     DWORD rc;
829     HRESULT r;
830
831     hdll=GetModuleHandleA("shell32.dll");
832     pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion");
833     if (pDllGetVersion)
834     {
835         dllver.cbSize=sizeof(dllver);
836         pDllGetVersion(&dllver);
837         trace("major=%ld minor=%ld build=%ld platform=%ld\n",
838               dllver.dwMajorVersion, dllver.dwMinorVersion,
839               dllver.dwBuildNumber, dllver.dwPlatformID);
840     }
841     else
842     {
843         memset(&dllver, 0, sizeof(dllver));
844     }
845
846     r = CoInitialize(NULL);
847     ok(SUCCEEDED(r), "CoInitialize failed (0x%08lx)\n", r);
848     if (!SUCCEEDED(r))
849         exit(1);
850
851     rc=GetModuleFileName(NULL, argv0, sizeof(argv0));
852     assert(rc!=0 && rc<sizeof(argv0));
853     if (GetFileAttributes(argv0)==INVALID_FILE_ATTRIBUTES)
854     {
855         strcat(argv0, ".so");
856         ok(GetFileAttributes(argv0)!=INVALID_FILE_ATTRIBUTES,
857            "unable to find argv0!\n");
858     }
859
860     GetTempPathA(sizeof(tmpdir)/sizeof(*tmpdir), tmpdir);
861     assert(GetTempFileNameA(tmpdir, "wt", 0, child_file)!=0);
862     init_event(child_file);
863
864     /* Set up the test files */
865     testfile=testfiles;
866     while (*testfile)
867     {
868         HANDLE hfile;
869
870         sprintf(filename, *testfile, tmpdir);
871         hfile=CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
872                      FILE_ATTRIBUTE_NORMAL, NULL);
873         if (hfile==INVALID_HANDLE_VALUE)
874         {
875             trace("unable to create '%s': err=%ld\n", filename, GetLastError());
876             assert(0);
877         }
878         CloseHandle(hfile);
879         testfile++;
880     }
881
882     /* Setup the test shortcuts */
883     sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir);
884     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
885     desc.description=NULL;
886     desc.workdir=NULL;
887     sprintf(filename, "%s\\test file.shlexec", tmpdir);
888     desc.path=filename;
889     desc.pidl=NULL;
890     desc.arguments="ignored";
891     desc.showcmd=0;
892     desc.icon=NULL;
893     desc.icon_id=0;
894     desc.hotkey=0;
895     create_lnk(lnkfile, &desc, 0);
896
897     sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir);
898     MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile));
899     desc.description=NULL;
900     desc.workdir=NULL;
901     desc.path=argv0;
902     desc.pidl=NULL;
903     sprintf(params, "shlexec \"%s\" Lnk", child_file);
904     desc.arguments=params;
905     desc.showcmd=0;
906     desc.icon=NULL;
907     desc.icon_id=0;
908     desc.hotkey=0;
909     create_lnk(lnkfile, &desc, 0);
910
911     /* Create a basic association suitable for most tests */
912     create_test_association(".shlexec");
913     create_test_verb(".shlexec", "Open", "Open \"%1\"");
914     create_test_verb(".shlexec", "NoQuotes", "NoQuotes %1");
915     create_test_verb(".shlexec", "LowerL", "LowerL %l");
916     create_test_verb(".shlexec", "QuotedLowerL", "QuotedLowerL \"%l\"");
917     create_test_verb(".shlexec", "UpperL", "UpperL %L");
918     create_test_verb(".shlexec", "QuotedUpperL", "QuotedUpperL \"%L\"");
919 }
920
921 static void cleanup_test()
922 {
923     char filename[MAX_PATH];
924     const char* const * testfile;
925
926     /* Delete the test files */
927     testfile=testfiles;
928     while (*testfile)
929     {
930         sprintf(filename, *testfile, tmpdir);
931         DeleteFile(filename);
932         testfile++;
933     }
934     DeleteFile(child_file);
935
936     /* Delete the test association */
937     delete_test_association(".shlexec");
938
939     CloseHandle(hEvent);
940
941     CoUninitialize();
942 }
943
944 START_TEST(shlexec)
945 {
946
947     myARGC = winetest_get_mainargs(&myARGV);
948     if (myARGC >= 3)
949     {
950         doChild(myARGC, myARGV);
951         exit(0);
952     }
953
954     init_test();
955
956     test_filename();
957     test_lnks();
958     test_exes();
959
960     cleanup_test();
961 }