1 /* Unit test suite for SHLWAPI Compact List and IStream ordinal functions
2  *
3  * Copyright 2002 Jon Griffiths
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #define COBJMACROS
21 #include <stdarg.h>
22 
23 #include "wine/test.h"
24 #include "windef.h"
25 #include "winbase.h"
26 #include "objbase.h"
27 
28 typedef struct tagSHLWAPI_CLIST
29 {
30   ULONG ulSize;
31   ULONG ulId;
32 } SHLWAPI_CLIST, *LPSHLWAPI_CLIST;
33 
34 typedef const SHLWAPI_CLIST* LPCSHLWAPI_CLIST;
35 
36 /* Items to add */
37 static const SHLWAPI_CLIST SHLWAPI_CLIST_items[] =
38 {
39   {4, 1},
40   {8, 3},
41   {12, 2},
42   {16, 8},
43   {20, 9},
44   {3, 11},
45   {9, 82},
46   {33, 16},
47   {32, 55},
48   {24, 100},
49   {39, 116},
50   { 0, 0}
51 };
52 
53 /* Dummy IStream object for testing calls */
54 struct dummystream
55 {
56   IStream IStream_iface;
57   LONG  ref;
58   int   readcalls;
59   BOOL  failreadcall;
60   BOOL  failreadsize;
61   BOOL  readbeyondend;
62   BOOL  readreturnlarge;
63   int   writecalls;
64   BOOL  failwritecall;
65   BOOL  failwritesize;
66   int   seekcalls;
67   int   statcalls;
68   BOOL  failstatcall;
69   LPCSHLWAPI_CLIST item;
70   ULARGE_INTEGER   pos;
71 };
72 
73 static inline struct dummystream *impl_from_IStream(IStream *iface)
74 {
75     return CONTAINING_RECORD(iface, struct dummystream, IStream_iface);
76 }
77 
78 static HRESULT WINAPI QueryInterface(IStream *iface, REFIID riid, void **ret_iface)
79 {
80     if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IStream, riid)) {
81         *ret_iface = iface;
82         IStream_AddRef(iface);
83         return S_OK;
84     }
85     trace("Unexpected REFIID %s\n", wine_dbgstr_guid(riid));
86     *ret_iface = NULL;
87     return E_NOINTERFACE;
88 }
89 
90 static ULONG WINAPI AddRef(IStream *iface)
91 {
92     struct dummystream *This = impl_from_IStream(iface);
93 
94     return InterlockedIncrement(&This->ref);
95 }
96 
97 static ULONG WINAPI Release(IStream *iface)
98 {
99     struct dummystream *This = impl_from_IStream(iface);
100 
101     return InterlockedDecrement(&This->ref);
102 }
103 
104 static HRESULT WINAPI Read(IStream *iface, void *lpMem, ULONG ulSize, ULONG *lpRead)
105 {
106   struct dummystream *This = impl_from_IStream(iface);
107   HRESULT hRet = S_OK;
108 
109   ++This->readcalls;
110   if (This->failreadcall)
111   {
112     return STG_E_ACCESSDENIED;
113   }
114   else if (This->failreadsize)
115   {
116     *lpRead = ulSize + 8;
117     return S_OK;
118   }
119   else if (This->readreturnlarge)
120   {
121     *((ULONG*)lpMem) = 0xffff01;
122     *lpRead = ulSize;
123     This->readreturnlarge = FALSE;
124     return S_OK;
125   }
126   if (ulSize == sizeof(ULONG))
127   {
128     /* Read size of item */
129     *((ULONG*)lpMem) = This->item->ulSize ? This->item->ulSize + sizeof(SHLWAPI_CLIST) : 0;
130     *lpRead = ulSize;
131   }
132   else
133   {
134     unsigned int i;
135     char* buff = lpMem;
136 
137     /* Read item data */
138     if (!This->item->ulSize)
139     {
140       This->readbeyondend = TRUE;
141       *lpRead = 0;
142       return E_FAIL; /* Should never happen */
143     }
144     *((ULONG*)lpMem) = This->item->ulId;
145     *lpRead = ulSize;
146 
147     for (i = 0; i < This->item->ulSize; i++)
148       buff[4+i] = i*2;
149 
150     This->item++;
151   }
152   return hRet;
153 }
154 
155 static HRESULT WINAPI Write(IStream *iface, const void *lpMem, ULONG ulSize, ULONG *lpWritten)
156 {
157   struct dummystream *This = impl_from_IStream(iface);
158   HRESULT hRet = S_OK;
159 
160   ++This->writecalls;
161   if (This->failwritecall)
162   {
163     return STG_E_ACCESSDENIED;
164   }
165   else if (This->failwritesize)
166   {
167     *lpWritten = 0;
168   }
169   else
170     *lpWritten = ulSize;
171   return hRet;
172 }
173 
174 static HRESULT WINAPI Seek(IStream *iface, LARGE_INTEGER dlibMove, DWORD dwOrigin,
175         ULARGE_INTEGER *plibNewPosition)
176 {
177   struct dummystream *This = impl_from_IStream(iface);
178 
179   ++This->seekcalls;
180   This->pos.QuadPart = dlibMove.QuadPart;
181   if (plibNewPosition)
182     plibNewPosition->QuadPart = dlibMove.QuadPart;
183   return S_OK;
184 }
185 
186 static HRESULT WINAPI Stat(IStream *iface, STATSTG *pstatstg, DWORD grfStatFlag)
187 {
188   struct dummystream *This = impl_from_IStream(iface);
189 
190   ++This->statcalls;
191   if (This->failstatcall)
192     return E_FAIL;
193   if (pstatstg)
194     pstatstg->cbSize.QuadPart = This->pos.QuadPart;
195   return S_OK;
196 }
197 
198 /* VTable */
199 static IStreamVtbl iclvt =
200 {
201   QueryInterface,
202   AddRef,
203   Release,
204   Read,
205   Write,
206   Seek,
207   NULL, /* SetSize */
208   NULL, /* CopyTo */
209   NULL, /* Commit */
210   NULL, /* Revert */
211   NULL, /* LockRegion */
212   NULL, /* UnlockRegion */
213   Stat,
214   NULL  /* Clone */
215 };
216 
217 /* Function ptrs for ordinal calls */
218 static HMODULE SHLWAPI_hshlwapi = 0;
219 
220 static VOID    (WINAPI *pSHLWAPI_19)(LPSHLWAPI_CLIST);
221 static BOOL    (WINAPI *pSHLWAPI_20)(LPSHLWAPI_CLIST*,LPCSHLWAPI_CLIST);
222 static BOOL    (WINAPI *pSHLWAPI_21)(LPSHLWAPI_CLIST*,ULONG);
223 static LPSHLWAPI_CLIST (WINAPI *pSHLWAPI_22)(LPSHLWAPI_CLIST,ULONG);
224 static HRESULT (WINAPI *pSHLWAPI_17)(IStream*, SHLWAPI_CLIST*);
225 static HRESULT (WINAPI *pSHLWAPI_18)(IStream*, SHLWAPI_CLIST**);
226 
227 static BOOL    (WINAPI *pSHLWAPI_166)(IStream*);
228 static HRESULT (WINAPI *pSHLWAPI_184)(IStream*, void*, ULONG);
229 static HRESULT (WINAPI *pSHLWAPI_212)(IStream*, const void*, ULONG);
230 static HRESULT (WINAPI *pSHLWAPI_213)(IStream*);
231 static HRESULT (WINAPI *pSHLWAPI_214)(IStream*, ULARGE_INTEGER*);
232 
233 
234 static BOOL InitFunctionPtrs(void)
235 {
236   SHLWAPI_hshlwapi = GetModuleHandleA("shlwapi.dll");
237 
238   /* SHCreateStreamOnFileEx was introduced in shlwapi v6.0 */
239   if(!GetProcAddress(SHLWAPI_hshlwapi, "SHCreateStreamOnFileEx")){
240       win_skip("Too old shlwapi version\n");
241       return FALSE;
242   }
243 
244   pSHLWAPI_17 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)17);
245   ok(pSHLWAPI_17 != 0, "No Ordinal 17\n");
246   pSHLWAPI_18 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)18);
247   ok(pSHLWAPI_18 != 0, "No Ordinal 18\n");
248   pSHLWAPI_19 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)19);
249   ok(pSHLWAPI_19 != 0, "No Ordinal 19\n");
250   pSHLWAPI_20 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)20);
251   ok(pSHLWAPI_20 != 0, "No Ordinal 20\n");
252   pSHLWAPI_21 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)21);
253   ok(pSHLWAPI_21 != 0, "No Ordinal 21\n");
254   pSHLWAPI_22 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)22);
255   ok(pSHLWAPI_22 != 0, "No Ordinal 22\n");
256   pSHLWAPI_166 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)166);
257   ok(pSHLWAPI_166 != 0, "No Ordinal 166\n");
258   pSHLWAPI_184 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)184);
259   ok(pSHLWAPI_184 != 0, "No Ordinal 184\n");
260   pSHLWAPI_212 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)212);
261   ok(pSHLWAPI_212 != 0, "No Ordinal 212\n");
262   pSHLWAPI_213 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)213);
263   ok(pSHLWAPI_213 != 0, "No Ordinal 213\n");
264   pSHLWAPI_214 = (void *)GetProcAddress( SHLWAPI_hshlwapi, (LPSTR)214);
265   ok(pSHLWAPI_214 != 0, "No Ordinal 214\n");
266 
267   return TRUE;
268 }
269 
270 static void InitDummyStream(struct dummystream *obj)
271 {
272     obj->IStream_iface.lpVtbl = &iclvt;
273     obj->ref = 1;
274     obj->readcalls = 0;
275     obj->failreadcall = FALSE;
276     obj->failreadsize = FALSE;
277     obj->readbeyondend = FALSE;
278     obj->readreturnlarge = FALSE;
279     obj->writecalls = 0;
280     obj->failwritecall = FALSE;
281     obj->failwritesize = FALSE;
282     obj->seekcalls = 0;
283     obj->statcalls = 0;
284     obj->failstatcall = FALSE;
285     obj->item = SHLWAPI_CLIST_items;
286     obj->pos.QuadPart = 0;
287 }
288 
289 
290 static void test_CList(void)
291 {
292   struct dummystream streamobj;
293   LPSHLWAPI_CLIST list = NULL;
294   LPCSHLWAPI_CLIST item = SHLWAPI_CLIST_items;
295   BOOL bRet;
296   HRESULT hRet;
297   LPSHLWAPI_CLIST inserted;
298   BYTE buff[64];
299   unsigned int i;
300 
301   if (!pSHLWAPI_17 || !pSHLWAPI_18 || !pSHLWAPI_19 || !pSHLWAPI_20 ||
302       !pSHLWAPI_21 || !pSHLWAPI_22)
303     return;
304 
305   /* Populate a list and test the items are added correctly */
306   while (item->ulSize)
307   {
308     /* Create item and fill with data */
309     inserted = (LPSHLWAPI_CLIST)buff;
310     inserted->ulSize = item->ulSize + sizeof(SHLWAPI_CLIST);
311     inserted->ulId = item->ulId;
312     for (i = 0; i < item->ulSize; i++)
313       buff[sizeof(SHLWAPI_CLIST)+i] = i*2;
314 
315     /* Add it */
316     bRet = pSHLWAPI_20(&list, inserted);
317     ok(bRet == TRUE, "failed list add\n");
318 
319     if (bRet == TRUE)
320     {
321       ok(list && list->ulSize, "item not added\n");
322 
323       /* Find it */
324       inserted = pSHLWAPI_22(list, item->ulId);
325       ok(inserted != NULL, "lost after adding\n");
326 
327       ok(!inserted || inserted->ulId != ~0U, "find returned a container\n");
328 
329       /* Check size */
330       if (inserted && inserted->ulSize & 0x3)
331       {
332         /* Contained */
333         ok(inserted[-1].ulId == ~0U, "invalid size is not countained\n");
334         ok(inserted[-1].ulSize > inserted->ulSize+sizeof(SHLWAPI_CLIST),
335            "container too small\n");
336       }
337       else if (inserted)
338       {
339         ok(inserted->ulSize==item->ulSize+sizeof(SHLWAPI_CLIST),
340            "id %d wrong size %d\n", inserted->ulId, inserted->ulSize);
341       }
342       if (inserted)
343       {
344         BOOL bDataOK = TRUE;
345         LPBYTE bufftest = (LPBYTE)inserted;
346 
347         for (i = 0; i < inserted->ulSize - sizeof(SHLWAPI_CLIST); i++)
348           if (bufftest[sizeof(SHLWAPI_CLIST)+i] != i*2)
349             bDataOK = FALSE;
350 
351         ok(bDataOK == TRUE, "data corrupted on insert\n");
352       }
353       ok(!inserted || inserted->ulId==item->ulId, "find got wrong item\n");
354     }
355     item++;
356   }
357 
358   /* Write the list */
359   InitDummyStream(&streamobj);
360 
361   hRet = pSHLWAPI_17(&streamobj.IStream_iface, list);
362   ok(hRet == S_OK, "write failed\n");
363   if (hRet == S_OK)
364   {
365     /* 1 call for each element, + 1 for OK (use our null element for this) */
366     ok(streamobj.writecalls == ARRAY_SIZE(SHLWAPI_CLIST_items), "wrong call count\n");
367     ok(streamobj.readcalls == 0,"called Read() in write\n");
368     ok(streamobj.seekcalls == 0,"called Seek() in write\n");
369   }
370 
371   /* Failure cases for writing */
372   InitDummyStream(&streamobj);
373   streamobj.failwritecall = TRUE;
374   hRet = pSHLWAPI_17(&streamobj.IStream_iface, list);
375   ok(hRet == STG_E_ACCESSDENIED, "changed object failure return\n");
376   ok(streamobj.writecalls == 1, "called object after failure\n");
377   ok(streamobj.readcalls == 0,"called Read() after failure\n");
378   ok(streamobj.seekcalls == 0,"called Seek() after failure\n");
379 
380   InitDummyStream(&streamobj);
381   streamobj.failwritesize = TRUE;
382   hRet = pSHLWAPI_17(&streamobj.IStream_iface, list);
383   ok(hRet == STG_E_MEDIUMFULL || broken(hRet == E_FAIL) /* Win7 */,
384      "changed size failure return\n");
385   ok(streamobj.writecalls == 1, "called object after size failure\n");
386   ok(streamobj.readcalls == 0,"called Read() after failure\n");
387   ok(streamobj.seekcalls == 0,"called Seek() after failure\n");
388 
389   /* Invalid inputs for adding */
390   inserted = (LPSHLWAPI_CLIST)buff;
391   inserted->ulSize = sizeof(SHLWAPI_CLIST) -1;
392   inserted->ulId = 33;
393   bRet = pSHLWAPI_20(&list, inserted);
394   ok(bRet == FALSE, "Expected failure\n");
395 
396   inserted = pSHLWAPI_22(list, 33);
397   ok(inserted == NULL, "inserted bad element size\n");
398 
399   inserted = (LPSHLWAPI_CLIST)buff;
400   inserted->ulSize = 44;
401   inserted->ulId = ~0U;
402   bRet = pSHLWAPI_20(&list, inserted);
403   ok(bRet == FALSE, "Expected failure\n");
404 
405   item = SHLWAPI_CLIST_items;
406 
407   /* Look for nonexistent item in populated list */
408   inserted = pSHLWAPI_22(list, 99999999);
409   ok(inserted == NULL, "found a nonexistent item\n");
410 
411   while (item->ulSize)
412   {
413     /* Delete items */
414     BOOL bRet = pSHLWAPI_21(&list, item->ulId);
415     ok(bRet == TRUE, "couldn't find item to delete\n");
416     item++;
417   }
418 
419   /* Look for nonexistent item in empty list */
420   inserted = pSHLWAPI_22(list, 99999999);
421   ok(inserted == NULL, "found an item in empty list\n");
422 
423   /* Create a list by reading in data */
424   InitDummyStream(&streamobj);
425 
426   hRet = pSHLWAPI_18(&streamobj.IStream_iface, &list);
427   ok(hRet == S_OK, "failed create from Read()\n");
428   if (hRet == S_OK)
429   {
430     ok(streamobj.readbeyondend == FALSE, "read beyond end\n");
431     /* 2 calls per item, but only 1 for the terminator */
432     ok(streamobj.readcalls == ARRAY_SIZE(SHLWAPI_CLIST_items) * 2 - 1, "wrong call count\n");
433     ok(streamobj.writecalls == 0, "called Write() from create\n");
434     ok(streamobj.seekcalls == 0,"called Seek() from create\n");
435 
436     item = SHLWAPI_CLIST_items;
437 
438     /* Check the items were added correctly */
439     while (item->ulSize)
440     {
441       inserted = pSHLWAPI_22(list, item->ulId);
442       ok(inserted != NULL, "lost after adding\n");
443 
444       ok(!inserted || inserted->ulId != ~0U, "find returned a container\n");
445 
446       /* Check size */
447       if (inserted && inserted->ulSize & 0x3)
448       {
449         /* Contained */
450         ok(inserted[-1].ulId == ~0U, "invalid size is not countained\n");
451         ok(inserted[-1].ulSize > inserted->ulSize+sizeof(SHLWAPI_CLIST),
452            "container too small\n");
453       }
454       else if (inserted)
455       {
456         ok(inserted->ulSize==item->ulSize+sizeof(SHLWAPI_CLIST),
457            "id %d wrong size %d\n", inserted->ulId, inserted->ulSize);
458       }
459       ok(!inserted || inserted->ulId==item->ulId, "find got wrong item\n");
460       if (inserted)
461       {
462         BOOL bDataOK = TRUE;
463         LPBYTE bufftest = (LPBYTE)inserted;
464 
465         for (i = 0; i < inserted->ulSize - sizeof(SHLWAPI_CLIST); i++)
466           if (bufftest[sizeof(SHLWAPI_CLIST)+i] != i*2)
467             bDataOK = FALSE;
468 
469         ok(bDataOK == TRUE, "data corrupted on insert\n");
470       }
471       item++;
472     }
473   }
474 
475   /* Failure cases for reading */
476   InitDummyStream(&streamobj);
477   streamobj.failreadcall = TRUE;
478   hRet = pSHLWAPI_18(&streamobj.IStream_iface, &list);
479   ok(hRet == STG_E_ACCESSDENIED, "changed object failure return\n");
480   ok(streamobj.readbeyondend == FALSE, "read beyond end\n");
481   ok(streamobj.readcalls == 1, "called object after read failure\n");
482   ok(streamobj.writecalls == 0,"called Write() after read failure\n");
483   ok(streamobj.seekcalls == 0,"called Seek() after read failure\n");
484 
485   /* Read returns large object */
486   InitDummyStream(&streamobj);
487   streamobj.readreturnlarge = TRUE;
488   hRet = pSHLWAPI_18(&streamobj.IStream_iface, &list);
489   ok(hRet == S_OK, "failed create from Read() with large item\n");
490   ok(streamobj.readbeyondend == FALSE, "read beyond end\n");
491   ok(streamobj.readcalls == 1,"wrong call count\n");
492   ok(streamobj.writecalls == 0,"called Write() after read failure\n");
493   ok(streamobj.seekcalls == 2,"wrong Seek() call count (%d)\n", streamobj.seekcalls);
494 
495   pSHLWAPI_19(list);
496 }
497 
498 static BOOL test_SHLWAPI_166(void)
499 {
500   struct dummystream streamobj;
501   BOOL bRet;
502 
503   if (!pSHLWAPI_166)
504     return FALSE;
505 
506   InitDummyStream(&streamobj);
507   bRet = pSHLWAPI_166(&streamobj.IStream_iface);
508 
509   if (bRet != TRUE)
510     return FALSE; /* This version doesn't support stream ops on clists */
511 
512   ok(streamobj.readcalls == 0, "called Read()\n");
513   ok(streamobj.writecalls == 0, "called Write()\n");
514   ok(streamobj.seekcalls == 0, "called Seek()\n");
515   ok(streamobj.statcalls == 1, "wrong call count\n");
516 
517   streamobj.statcalls = 0;
518   streamobj.pos.QuadPart = 50001;
519 
520   bRet = pSHLWAPI_166(&streamobj.IStream_iface);
521 
522   ok(bRet == FALSE, "failed after seek adjusted\n");
523   ok(streamobj.readcalls == 0, "called Read()\n");
524   ok(streamobj.writecalls == 0, "called Write()\n");
525   ok(streamobj.seekcalls == 0, "called Seek()\n");
526   ok(streamobj.statcalls == 1, "wrong call count\n");
527 
528   /* Failure cases */
529   InitDummyStream(&streamobj);
530   streamobj.pos.QuadPart = 50001;
531   streamobj.failstatcall = TRUE; /* 1: Stat() Bad, Read() OK */
532   bRet = pSHLWAPI_166(&streamobj.IStream_iface);
533   ok(bRet == FALSE, "should be FALSE after read is OK\n");
534   ok(streamobj.readcalls == 1, "wrong call count\n");
535   ok(streamobj.writecalls == 0, "called Write()\n");
536   ok(streamobj.seekcalls == 1, "wrong call count\n");
537   ok(streamobj.statcalls == 1, "wrong call count\n");
538   ok(streamobj.pos.QuadPart == 0, "Didn't seek to start\n");
539 
540   InitDummyStream(&streamobj);
541   streamobj.pos.QuadPart = 50001;
542   streamobj.failstatcall = TRUE;
543   streamobj.failreadcall = TRUE; /* 2: Stat() Bad, Read() Bad Also */
544   bRet = pSHLWAPI_166(&streamobj.IStream_iface);
545   ok(bRet == TRUE, "Should be true after read fails\n");
546   ok(streamobj.readcalls == 1, "wrong call count\n");
547   ok(streamobj.writecalls == 0, "called Write()\n");
548   ok(streamobj.seekcalls == 0, "Called Seek()\n");
549   ok(streamobj.statcalls == 1, "wrong call count\n");
550   ok(streamobj.pos.QuadPart == 50001, "called Seek() after read failed\n");
551   return TRUE;
552 }
553 
554 static void test_SHLWAPI_184(void)
555 {
556   struct dummystream streamobj;
557   char buff[256];
558   HRESULT hRet;
559 
560   if (!pSHLWAPI_184)
561     return;
562 
563   InitDummyStream(&streamobj);
564   hRet = pSHLWAPI_184(&streamobj.IStream_iface, buff, sizeof(buff));
565 
566   ok(hRet == S_OK, "failed Read()\n");
567   ok(streamobj.readcalls == 1, "wrong call count\n");
568   ok(streamobj.writecalls == 0, "called Write()\n");
569   ok(streamobj.seekcalls == 0, "called Seek()\n");
570 }
571 
572 static void test_SHLWAPI_212(void)
573 {
574   struct dummystream streamobj;
575   char buff[256];
576   HRESULT hRet;
577 
578   if (!pSHLWAPI_212)
579     return;
580 
581   InitDummyStream(&streamobj);
582   hRet = pSHLWAPI_212(&streamobj.IStream_iface, buff, sizeof(buff));
583 
584   ok(hRet == S_OK, "failed Write()\n");
585   ok(streamobj.readcalls == 0, "called Read()\n");
586   ok(streamobj.writecalls == 1, "wrong call count\n");
587   ok(streamobj.seekcalls == 0, "called Seek()\n");
588 }
589 
590 static void test_SHLWAPI_213(void)
591 {
592   struct dummystream streamobj;
593   ULARGE_INTEGER ul;
594   LARGE_INTEGER ll;
595   HRESULT hRet;
596 
597   if (!pSHLWAPI_213 || !pSHLWAPI_214)
598     return;
599 
600   InitDummyStream(&streamobj);
601   ll.QuadPart = 5000l;
602   Seek(&streamobj.IStream_iface, ll, 0, NULL); /* Seek to 5000l */
603 
604   streamobj.seekcalls = 0;
605   pSHLWAPI_213(&streamobj.IStream_iface); /* Should rewind */
606   ok(streamobj.statcalls == 0, "called Stat()\n");
607   ok(streamobj.readcalls == 0, "called Read()\n");
608   ok(streamobj.writecalls == 0, "called Write()\n");
609   ok(streamobj.seekcalls == 1, "wrong call count\n");
610 
611   ul.QuadPart = 50001;
612   hRet = pSHLWAPI_214(&streamobj.IStream_iface, &ul);
613   ok(hRet == S_OK, "failed Stat()\n");
614   ok(ul.QuadPart == 0, "213 didn't rewind stream\n");
615 }
616 
617 static void test_SHLWAPI_214(void)
618 {
619   struct dummystream streamobj;
620   ULARGE_INTEGER ul;
621   LARGE_INTEGER ll;
622   HRESULT hRet;
623 
624   if (!pSHLWAPI_214)
625     return;
626 
627   InitDummyStream(&streamobj);
628   ll.QuadPart = 5000l;
629   Seek(&streamobj.IStream_iface, ll, 0, NULL);
630   ul.QuadPart = 0;
631   streamobj.seekcalls = 0;
632   hRet = pSHLWAPI_214(&streamobj.IStream_iface, &ul);
633 
634   ok(hRet == S_OK, "failed Stat()\n");
635   ok(streamobj.statcalls == 1, "wrong call count\n");
636   ok(streamobj.readcalls == 0, "called Read()\n");
637   ok(streamobj.writecalls == 0, "called Write()\n");
638   ok(streamobj.seekcalls == 0, "called Seek()\n");
639   ok(ul.QuadPart == 5000l, "Stat gave wrong size\n");
640 }
641 
642 START_TEST(clist)
643 {
644   if(!InitFunctionPtrs())
645     return;
646 
647   test_CList();
648 
649   /* Test streaming if this version supports it */
650   if (test_SHLWAPI_166())
651   {
652     test_SHLWAPI_184();
653     test_SHLWAPI_212();
654     test_SHLWAPI_213();
655     test_SHLWAPI_214();
656   }
657 }
658