|  | /* | 
|  | * Unit tests for DPA functions | 
|  | * | 
|  | * Copyright 2003 Uwe Bonnes | 
|  | * Copyright 2005 Felix Nawothnig | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, write to the Free Software | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | #define COBJMACROS | 
|  |  | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "winuser.h" | 
|  | #include "commctrl.h" | 
|  | #include "objidl.h" | 
|  |  | 
|  | #include "wine/test.h" | 
|  |  | 
|  | #define expect(expected, got) ok(got == expected, "Expected %d, got %d\n", expected, got) | 
|  |  | 
|  | typedef struct _STREAMDATA | 
|  | { | 
|  | DWORD dwSize; | 
|  | DWORD dwData2; | 
|  | DWORD dwItems; | 
|  | } STREAMDATA, *PSTREAMDATA; | 
|  |  | 
|  | static HDPA    (WINAPI *pDPA_Clone)(const HDPA,const HDPA); | 
|  | static HDPA    (WINAPI *pDPA_Create)(INT); | 
|  | static HDPA    (WINAPI *pDPA_CreateEx)(INT,HANDLE); | 
|  | static PVOID   (WINAPI *pDPA_DeleteAllPtrs)(const HDPA); | 
|  | static PVOID   (WINAPI *pDPA_DeletePtr)(const HDPA,INT); | 
|  | static BOOL    (WINAPI *pDPA_Destroy)(const HDPA); | 
|  | static VOID    (WINAPI *pDPA_DestroyCallback)(HDPA,PFNDPAENUMCALLBACK,PVOID); | 
|  | static VOID    (WINAPI *pDPA_EnumCallback)(HDPA,PFNDPAENUMCALLBACK,PVOID); | 
|  | static INT     (WINAPI *pDPA_GetPtr)(const HDPA,INT); | 
|  | static INT     (WINAPI *pDPA_GetPtrIndex)(const HDPA,PVOID); | 
|  | static BOOL    (WINAPI *pDPA_Grow)(HDPA,INT); | 
|  | static INT     (WINAPI *pDPA_InsertPtr)(const HDPA,INT,PVOID); | 
|  | static HRESULT (WINAPI *pDPA_LoadStream)(HDPA*,PFNDPASTREAM,IStream*,LPVOID); | 
|  | static BOOL    (WINAPI *pDPA_Merge)(const HDPA,const HDPA,DWORD,PFNDPACOMPARE,PFNDPAMERGE,LPARAM); | 
|  | static HRESULT (WINAPI *pDPA_SaveStream)(HDPA,PFNDPASTREAM,IStream*,LPVOID); | 
|  | static INT     (WINAPI *pDPA_Search)(HDPA,PVOID,INT,PFNDPACOMPARE,LPARAM,UINT); | 
|  | static BOOL    (WINAPI *pDPA_SetPtr)(const HDPA,INT,PVOID); | 
|  | static BOOL    (WINAPI *pDPA_Sort)(const HDPA,PFNDPACOMPARE,LPARAM); | 
|  |  | 
|  | #define COMCTL32_GET_PROC(func, ord) \ | 
|  | ((p ## func = (PVOID)GetProcAddress(hcomctl32,(LPCSTR)ord)) ? 1 \ | 
|  | : (trace( #func " not exported\n"), 0)) | 
|  |  | 
|  | static BOOL InitFunctionPtrs(HMODULE hcomctl32) | 
|  | { | 
|  | /* 4.00+ */ | 
|  | if(COMCTL32_GET_PROC(DPA_Clone, 331) && | 
|  | COMCTL32_GET_PROC(DPA_Create, 328) && | 
|  | COMCTL32_GET_PROC(DPA_CreateEx, 340) && | 
|  | COMCTL32_GET_PROC(DPA_DeleteAllPtrs, 337) && | 
|  | COMCTL32_GET_PROC(DPA_DeletePtr, 336) && | 
|  | COMCTL32_GET_PROC(DPA_Destroy, 329) && | 
|  | COMCTL32_GET_PROC(DPA_GetPtr, 332) && | 
|  | COMCTL32_GET_PROC(DPA_GetPtrIndex, 333) && | 
|  | COMCTL32_GET_PROC(DPA_Grow, 330) && | 
|  | COMCTL32_GET_PROC(DPA_InsertPtr, 334) && | 
|  | COMCTL32_GET_PROC(DPA_Search, 339) && | 
|  | COMCTL32_GET_PROC(DPA_SetPtr, 335) && | 
|  | COMCTL32_GET_PROC(DPA_Sort, 338)) | 
|  | { | 
|  | /* 4.71+ */ | 
|  | COMCTL32_GET_PROC(DPA_DestroyCallback, 386) && | 
|  | COMCTL32_GET_PROC(DPA_EnumCallback, 385) && | 
|  | COMCTL32_GET_PROC(DPA_LoadStream, 9) && | 
|  | COMCTL32_GET_PROC(DPA_Merge, 11) && | 
|  | COMCTL32_GET_PROC(DPA_SaveStream, 10); | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | /* Callbacks */ | 
|  | static INT CALLBACK CB_CmpLT(PVOID p1, PVOID p2, LPARAM lp) | 
|  | { | 
|  | ok(lp == 0xdeadbeef, "lp=%ld\n", lp); | 
|  | return p1 < p2 ? -1 : p1 > p2 ? 1 : 0; | 
|  | } | 
|  |  | 
|  | static INT CALLBACK CB_CmpGT(PVOID p1, PVOID p2, LPARAM lp) | 
|  | { | 
|  | ok(lp == 0xdeadbeef, "lp=%ld\n", lp); | 
|  | return p1 > p2 ? -1 : p1 < p2 ? 1 : 0; | 
|  | } | 
|  |  | 
|  | /* merge callback messages counter | 
|  | DPAMM_MERGE     1 | 
|  | DPAMM_DELETE    2 | 
|  | DPAMM_INSERT    3  */ | 
|  | static INT nMessages[4]; | 
|  |  | 
|  | static PVOID CALLBACK CB_MergeInsertSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp) | 
|  | { | 
|  | nMessages[op]++; | 
|  | ok(lp == 0xdeadbeef, "lp=%ld\n", lp); | 
|  | return p1; | 
|  | } | 
|  |  | 
|  | static PVOID CALLBACK CB_MergeDeleteOddSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp) | 
|  | { | 
|  | nMessages[op]++; | 
|  | ok(lp == 0xdeadbeef, "lp=%ld\n", lp); | 
|  | return ((PCHAR)p2)+1; | 
|  | } | 
|  |  | 
|  | static INT nEnum; | 
|  |  | 
|  | static INT CALLBACK CB_EnumFirstThree(PVOID pItem, PVOID lp) | 
|  | { | 
|  | INT i; | 
|  |  | 
|  | i = pDPA_GetPtrIndex(lp, pItem); | 
|  | ok(i == nEnum, "i=%d nEnum=%d\n", i, nEnum); | 
|  | nEnum++; | 
|  | pDPA_SetPtr(lp, i, (PVOID)7); | 
|  | return pItem != (PVOID)3; | 
|  | } | 
|  |  | 
|  | static HRESULT CALLBACK CB_Save(DPASTREAMINFO *pInfo, IStream *pStm, LPVOID lp) | 
|  | { | 
|  | HRESULT hRes; | 
|  |  | 
|  | ok(lp == (LPVOID)0xdeadbeef, "lp=%p\n", lp); | 
|  | hRes = IStream_Write(pStm, &pInfo->iPos, sizeof(INT), NULL); | 
|  | expect(S_OK, hRes); | 
|  | hRes = IStream_Write(pStm, &pInfo->pvItem, sizeof(PVOID), NULL); | 
|  | expect(S_OK, hRes); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT CALLBACK CB_Load(DPASTREAMINFO *pInfo, IStream *pStm, LPVOID lp) | 
|  | { | 
|  | HRESULT hRes; | 
|  | INT iOldPos; | 
|  |  | 
|  | iOldPos = pInfo->iPos; | 
|  | ok(lp == (LPVOID)0xdeadbeef, "lp=%p\n", lp); | 
|  | hRes = IStream_Read(pStm, &pInfo->iPos, sizeof(INT), NULL); | 
|  | expect(S_OK, hRes); | 
|  | ok(pInfo->iPos == iOldPos, "iPos=%d iOldPos=%d\n", pInfo->iPos, iOldPos); | 
|  | hRes = IStream_Read(pStm, &pInfo->pvItem, sizeof(PVOID), NULL); | 
|  | expect(S_OK, hRes); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static BOOL CheckDPA(HDPA dpa, DWORD dwIn, PDWORD pdwOut) | 
|  | { | 
|  | DWORD dwOut = 0; | 
|  | INT i; | 
|  |  | 
|  | for(i = 0; i < 8;) | 
|  | { | 
|  | ULONG_PTR ulItem = (ULONG_PTR)pDPA_GetPtr(dpa, i++); | 
|  | if(!ulItem) break; | 
|  | dwOut = dwOut << 4 | (ulItem & 0xf); | 
|  | } | 
|  |  | 
|  | *pdwOut = dwOut; | 
|  |  | 
|  | if(dwOut != dwIn) | 
|  | { | 
|  | pDPA_DeleteAllPtrs(dpa); | 
|  |  | 
|  | do | 
|  | { | 
|  | pDPA_InsertPtr(dpa, 0, (PVOID)(ULONG_PTR)(dwIn & 0xf)); | 
|  | dwIn >>= 4; | 
|  | } | 
|  | while(dwIn); | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static void test_dpa(void) | 
|  | { | 
|  | SYSTEM_INFO si; | 
|  | HANDLE hHeap; | 
|  | HDPA dpa, dpa2, dpa3; | 
|  | INT ret, i; | 
|  | PVOID p; | 
|  | DWORD dw, dw2, dw3; | 
|  | BOOL rc; | 
|  |  | 
|  | GetSystemInfo(&si); | 
|  | hHeap = HeapCreate(0, 1, 2); | 
|  | ok(hHeap != NULL, "error=%d\n", GetLastError()); | 
|  | dpa3 = pDPA_CreateEx(0, hHeap); | 
|  | ok(dpa3 != NULL, "\n"); | 
|  | ret = pDPA_Grow(dpa3, si.dwPageSize + 1); | 
|  | ok(!ret && GetLastError() == ERROR_NOT_ENOUGH_MEMORY, | 
|  | "ret=%d error=%d\n", ret, GetLastError()); | 
|  |  | 
|  | dpa = pDPA_Create(0); | 
|  | ok(dpa != NULL, "\n"); | 
|  |  | 
|  | /* Set item with out of bound index */ | 
|  | ok(pDPA_SetPtr(dpa, 1, (PVOID)6), "\n"); | 
|  | /* Fill the created gap */ | 
|  | ok(pDPA_SetPtr(dpa, 0, (PVOID)5), "\n"); | 
|  | rc=CheckDPA(dpa, 0x56, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | /* Prepend item */ | 
|  | ret = pDPA_InsertPtr(dpa, 1, (PVOID)1); | 
|  | ok(ret == 1, "ret=%d\n", ret); | 
|  | /* Append item using correct index */ | 
|  | ret = pDPA_InsertPtr(dpa, 3, (PVOID)3); | 
|  | ok(ret == 3, "ret=%d\n", ret); | 
|  | /* Append item using out of bound index */ | 
|  | ret = pDPA_InsertPtr(dpa, 5, (PVOID)2); | 
|  | ok(ret == 4, "ret=%d\n", ret); | 
|  | /* Append item using DPA_APPEND */ | 
|  | ret = pDPA_InsertPtr(dpa, DPA_APPEND, (PVOID)4); | 
|  | ok(ret == 5, "ret=%d\n", ret); | 
|  |  | 
|  | rc=CheckDPA(dpa, 0x516324, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | for(i = 1; i <= 6; i++) | 
|  | { | 
|  | INT j, k; | 
|  | k = pDPA_GetPtrIndex(dpa, (PVOID)(INT_PTR)i); | 
|  | /* Linear searches should work on unsorted DPAs */ | 
|  | j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpLT, 0xdeadbeef, 0); | 
|  | ok(j == k, "j=%d k=%d\n", j, k); | 
|  | } | 
|  |  | 
|  | /* Sort DPA */ | 
|  | ok(pDPA_Sort(dpa, CB_CmpGT, 0xdeadbeef), "\n"); | 
|  | rc=CheckDPA(dpa, 0x654321, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | /* Clone into a new DPA */ | 
|  | dpa2 = pDPA_Clone(dpa, NULL); | 
|  | ok(dpa2 != NULL, "\n"); | 
|  | /* The old data should have been preserved */ | 
|  | rc=CheckDPA(dpa2, 0x654321, &dw2); | 
|  | ok(rc, "dw=0x%x\n", dw2); | 
|  | ok(pDPA_Sort(dpa, CB_CmpLT, 0xdeadbeef), "\n"); | 
|  |  | 
|  | /* Test if the DPA itself was really copied */ | 
|  | rc=CheckDPA(dpa,  0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n",  dw ); | 
|  | rc=CheckDPA(dpa2, 0x654321, &dw2); | 
|  | ok(rc, "dw2=0x%x\n", dw2); | 
|  |  | 
|  | /* Clone into an old DPA */ | 
|  | p = NULL; SetLastError(ERROR_SUCCESS); | 
|  | p = pDPA_Clone(dpa, dpa3); | 
|  | ok(p == dpa3, "p=%p\n", p); | 
|  | rc=CheckDPA(dpa3, 0x123456, &dw3); | 
|  | ok(rc, "dw3=0x%x\n", dw3); | 
|  |  | 
|  | for(i = 1; i <= 6; i++) | 
|  | { | 
|  | INT j; | 
|  |  | 
|  | /* The array is in order so ptr == index+1 */ | 
|  | j = pDPA_GetPtrIndex(dpa, (PVOID)(INT_PTR)i); | 
|  | ok(j+1 == i, "j=%d i=%d\n", j, i); | 
|  | j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpLT, 0xdeadbeef, DPAS_SORTED); | 
|  | ok(j+1 == i, "j=%d i=%d\n", j, i); | 
|  |  | 
|  | /* Linear searches respect iStart ... */ | 
|  | j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, i+1, CB_CmpLT, 0xdeadbeef, 0); | 
|  | ok(j == DPA_ERR, "j=%d\n", j); | 
|  | /* ... but for a binary search it's ignored */ | 
|  | j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, i+1, CB_CmpLT, 0xdeadbeef, DPAS_SORTED); | 
|  | ok(j+1 == i, "j=%d i=%d\n", j, i); | 
|  | } | 
|  |  | 
|  | /* Try to get the index of a nonexistent item */ | 
|  | i = pDPA_GetPtrIndex(dpa, (PVOID)7); | 
|  | ok(i == DPA_ERR, "i=%d\n", i); | 
|  |  | 
|  | /* Try to delete out of bound indexes */ | 
|  | p = pDPA_DeletePtr(dpa, -1); | 
|  | ok(p == NULL, "p=%p\n", p); | 
|  | p = pDPA_DeletePtr(dpa, 6); | 
|  | ok(p == NULL, "p=%p\n", p); | 
|  |  | 
|  | /* Delete the third item */ | 
|  | p = pDPA_DeletePtr(dpa, 2); | 
|  | ok(p == (PVOID)3, "p=%p\n", p); | 
|  | rc=CheckDPA(dpa, 0x12456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | /* Check where to re-insert the deleted item */ | 
|  | i = pDPA_Search(dpa, (PVOID)3, 0, | 
|  | CB_CmpLT, 0xdeadbeef, DPAS_SORTED|DPAS_INSERTAFTER); | 
|  | ok(i == 2, "i=%d\n", i); | 
|  | /* DPAS_INSERTBEFORE works just like DPAS_INSERTAFTER */ | 
|  | i = pDPA_Search(dpa, (PVOID)3, 0, | 
|  | CB_CmpLT, 0xdeadbeef, DPAS_SORTED|DPAS_INSERTBEFORE); | 
|  | ok(i == 2, "i=%d\n", i); | 
|  | /* without DPAS_INSERTBEFORE/AFTER */ | 
|  | i = pDPA_Search(dpa, (PVOID)3, 0, | 
|  | CB_CmpLT, 0xdeadbeef, DPAS_SORTED); | 
|  | ok(i == -1, "i=%d\n", i); | 
|  |  | 
|  | /* Re-insert the item */ | 
|  | ret = pDPA_InsertPtr(dpa, 2, (PVOID)3); | 
|  | ok(ret == 2, "ret=%d i=%d\n", ret, 2); | 
|  | rc=CheckDPA(dpa, 0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | /* When doing a binary search while claiming reverse order all indexes | 
|  | * should be bogus */ | 
|  | for(i = 0; i < 6; i++) | 
|  | { | 
|  | INT j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpGT, 0xdeadbeef, | 
|  | DPAS_SORTED|DPAS_INSERTBEFORE); | 
|  | ok(j != i, "i=%d\n", i); | 
|  | } | 
|  |  | 
|  | /* Setting item with huge index should work */ | 
|  | ok(pDPA_SetPtr(dpa2, 0x12345, (PVOID)0xdeadbeef), "\n"); | 
|  | ret = pDPA_GetPtrIndex(dpa2, (PVOID)0xdeadbeef); | 
|  | ok(ret == 0x12345, "ret=%d\n", ret); | 
|  |  | 
|  | pDPA_DeleteAllPtrs(dpa2); | 
|  | rc=CheckDPA(dpa2, 0, &dw2); | 
|  | ok(rc, "dw2=0x%x\n", dw2); | 
|  |  | 
|  | pDPA_Destroy(dpa); | 
|  | pDPA_Destroy(dpa2); | 
|  | pDPA_Destroy(dpa3); | 
|  | } | 
|  |  | 
|  | static void test_DPA_Merge(void) | 
|  | { | 
|  | HDPA dpa, dpa2, dpa3; | 
|  | INT ret, i; | 
|  | DWORD dw; | 
|  | BOOL rc; | 
|  |  | 
|  | if(!pDPA_Merge) | 
|  | { | 
|  | win_skip("DPA_Merge() not available\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dpa  = pDPA_Create(0); | 
|  | dpa2 = pDPA_Create(0); | 
|  | dpa3 = pDPA_Create(0); | 
|  |  | 
|  | ret = pDPA_InsertPtr(dpa, 0, (PVOID)1); | 
|  | ok(ret == 0, "ret=%d\n", ret); | 
|  | ret = pDPA_InsertPtr(dpa, 1, (PVOID)3); | 
|  | ok(ret == 1, "ret=%d\n", ret); | 
|  | ret = pDPA_InsertPtr(dpa, 2, (PVOID)5); | 
|  | ok(ret == 2, "ret=%d\n", ret); | 
|  |  | 
|  | rc = CheckDPA(dpa, 0x135, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | for (i = 0; i < 6; i++) | 
|  | { | 
|  | ret = pDPA_InsertPtr(dpa2, i, (PVOID)(INT_PTR)(6-i)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | ret = pDPA_InsertPtr(dpa3, i, (PVOID)(INT_PTR)(i+1)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | } | 
|  |  | 
|  | rc = CheckDPA(dpa2, 0x654321, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  | rc = CheckDPA(dpa3, 0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | /* Delete all odd entries from dpa2 */ | 
|  | memset(nMessages, 0, sizeof(nMessages)); | 
|  | pDPA_Merge(dpa2, dpa, DPAM_INTERSECT, | 
|  | CB_CmpLT, CB_MergeDeleteOddSrc, 0xdeadbeef); | 
|  | rc = CheckDPA(dpa2, 0x246, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | expect(3, nMessages[DPAMM_MERGE]); | 
|  | expect(3, nMessages[DPAMM_DELETE]); | 
|  | expect(0, nMessages[DPAMM_INSERT]); | 
|  |  | 
|  | for (i = 0; i < 6; i++) | 
|  | { | 
|  | ret = pDPA_InsertPtr(dpa2, i, (PVOID)(INT_PTR)(6-i)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | } | 
|  |  | 
|  | /* DPAM_INTERSECT - returning source while merging */ | 
|  | memset(nMessages, 0, sizeof(nMessages)); | 
|  | pDPA_Merge(dpa2, dpa, DPAM_INTERSECT, | 
|  | CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); | 
|  | rc = CheckDPA(dpa2, 0x135, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | expect(3, nMessages[DPAMM_MERGE]); | 
|  | expect(6, nMessages[DPAMM_DELETE]); | 
|  | expect(0, nMessages[DPAMM_INSERT]); | 
|  |  | 
|  | /* DPAM_UNION */ | 
|  | pDPA_DeleteAllPtrs(dpa); | 
|  | pDPA_InsertPtr(dpa, 0, (PVOID)1); | 
|  | pDPA_InsertPtr(dpa, 1, (PVOID)3); | 
|  | pDPA_InsertPtr(dpa, 2, (PVOID)5); | 
|  | pDPA_DeleteAllPtrs(dpa2); | 
|  | pDPA_InsertPtr(dpa2, 0, (PVOID)2); | 
|  | pDPA_InsertPtr(dpa2, 1, (PVOID)4); | 
|  | pDPA_InsertPtr(dpa2, 2, (PVOID)6); | 
|  |  | 
|  | memset(nMessages, 0, sizeof(nMessages)); | 
|  | pDPA_Merge(dpa2, dpa, DPAM_UNION, | 
|  | CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); | 
|  | rc = CheckDPA(dpa2, 0x123456, &dw); | 
|  | ok(rc || | 
|  | broken(!rc && dw == 0x23456), /* 4.7x */ | 
|  | "dw=0x%x\n", dw); | 
|  |  | 
|  | expect(0, nMessages[DPAMM_MERGE]); | 
|  | expect(0, nMessages[DPAMM_DELETE]); | 
|  | ok(nMessages[DPAMM_INSERT] == 3 || | 
|  | broken(nMessages[DPAMM_INSERT] == 2), /* 4.7x */ | 
|  | "Expected 3, got %d\n", nMessages[DPAMM_INSERT]); | 
|  |  | 
|  | /* Merge dpa3 into dpa2 and dpa */ | 
|  | memset(nMessages, 0, sizeof(nMessages)); | 
|  | pDPA_Merge(dpa, dpa3, DPAM_UNION|DPAM_SORTED, | 
|  | CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); | 
|  | expect(3, nMessages[DPAMM_MERGE]); | 
|  | expect(0, nMessages[DPAMM_DELETE]); | 
|  | expect(3, nMessages[DPAMM_INSERT]); | 
|  |  | 
|  |  | 
|  | pDPA_DeleteAllPtrs(dpa2); | 
|  | pDPA_InsertPtr(dpa2, 0, (PVOID)2); | 
|  | pDPA_InsertPtr(dpa2, 1, (PVOID)4); | 
|  | pDPA_InsertPtr(dpa2, 2, (PVOID)6); | 
|  |  | 
|  | memset(nMessages, 0, sizeof(nMessages)); | 
|  | pDPA_Merge(dpa2, dpa3, DPAM_UNION|DPAM_SORTED, | 
|  | CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); | 
|  | expect(3, nMessages[DPAMM_MERGE]); | 
|  | expect(0, nMessages[DPAMM_DELETE]); | 
|  | ok(nMessages[DPAMM_INSERT] == 3 || | 
|  | broken(nMessages[DPAMM_INSERT] == 2), /* 4.7x */ | 
|  | "Expected 3, got %d\n", nMessages[DPAMM_INSERT]); | 
|  |  | 
|  | rc = CheckDPA(dpa,  0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n",  dw); | 
|  | rc = CheckDPA(dpa2, 0x123456, &dw); | 
|  | ok(rc || | 
|  | broken(!rc), /* win98 */ | 
|  | "dw=0x%x\n", dw); | 
|  | rc = CheckDPA(dpa3, 0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | pDPA_Destroy(dpa); | 
|  | pDPA_Destroy(dpa2); | 
|  | pDPA_Destroy(dpa3); | 
|  | } | 
|  |  | 
|  | static void test_DPA_EnumCallback(void) | 
|  | { | 
|  | HDPA dpa; | 
|  | BOOL rc; | 
|  | DWORD dw; | 
|  | INT i, ret; | 
|  |  | 
|  | if(!pDPA_EnumCallback) | 
|  | { | 
|  | win_skip("DPA_EnumCallback() not available\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dpa = pDPA_Create(0); | 
|  |  | 
|  | for (i = 0; i < 6; i++) | 
|  | { | 
|  | ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | } | 
|  |  | 
|  | rc = CheckDPA(dpa, 0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  |  | 
|  | nEnum = 0; | 
|  | /* test callback sets first 3 items to 7 */ | 
|  | pDPA_EnumCallback(dpa, CB_EnumFirstThree, dpa); | 
|  | rc = CheckDPA(dpa, 0x777456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  | ok(nEnum == 3, "nEnum=%d\n", nEnum); | 
|  |  | 
|  | pDPA_Destroy(dpa); | 
|  | } | 
|  |  | 
|  | static void test_DPA_DestroyCallback(void) | 
|  | { | 
|  | HDPA dpa; | 
|  | INT i, ret; | 
|  |  | 
|  | if(!pDPA_DestroyCallback) | 
|  | { | 
|  | win_skip("DPA_DestroyCallback() not available\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dpa = pDPA_Create(0); | 
|  |  | 
|  | for (i = 0; i < 3; i++) | 
|  | { | 
|  | ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | } | 
|  |  | 
|  | nEnum = 0; | 
|  | pDPA_DestroyCallback(dpa, CB_EnumFirstThree, dpa); | 
|  | ok(nEnum == 3, "nEnum=%d\n", nEnum); | 
|  | } | 
|  |  | 
|  | static void test_DPA_LoadStream(void) | 
|  | { | 
|  | static const WCHAR szStg[] = { 'S','t','g',0 }; | 
|  | IStorage* pStg = NULL; | 
|  | IStream* pStm = NULL; | 
|  | LARGE_INTEGER li; | 
|  | ULARGE_INTEGER uli; | 
|  | DWORD dwMode; | 
|  | HRESULT hRes; | 
|  | STREAMDATA header; | 
|  | ULONG written, ret; | 
|  | HDPA dpa; | 
|  |  | 
|  | if(!pDPA_LoadStream) | 
|  | { | 
|  | win_skip("DPA_LoadStream() not available. Skipping stream tests.\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | hRes = CoInitialize(NULL); | 
|  | if (hRes != S_OK) | 
|  | { | 
|  | ok(0, "hResult: %d\n", hRes); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dwMode = STGM_DIRECT|STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE; | 
|  | hRes = StgCreateDocfile(NULL, dwMode|STGM_DELETEONRELEASE, 0, &pStg); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = IStorage_CreateStream(pStg, szStg, dwMode, 0, 0, &pStm); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | /* write less than header size */ | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | memset(&header, 0, sizeof(header)); | 
|  | written = 0; | 
|  | uli.QuadPart = sizeof(header)-1; | 
|  | hRes = IStream_SetSize(pStm, uli); | 
|  | expect(S_OK, hRes); | 
|  | hRes = IStream_Write(pStm, &header, sizeof(header)-1, &written); | 
|  | expect(S_OK, hRes); | 
|  | written -= sizeof(header)-1; | 
|  | expect(0, written); | 
|  |  | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, NULL); | 
|  | expect(E_FAIL, hRes); | 
|  |  | 
|  | /* check stream position after header read failed */ | 
|  | li.QuadPart = 0; | 
|  | uli.QuadPart = 1; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_CUR, &uli); | 
|  | expect(S_OK, hRes); | 
|  | ok(uli.QuadPart == 0, "Expected to position reset\n"); | 
|  |  | 
|  | /* write valid header for empty DPA */ | 
|  | header.dwSize = sizeof(header); | 
|  | header.dwData2 = 1; | 
|  | header.dwItems = 0; | 
|  | written = 0; | 
|  |  | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | uli.QuadPart = sizeof(header); | 
|  | hRes = IStream_SetSize(pStm, uli); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = IStream_Write(pStm, &header, sizeof(header), &written); | 
|  | expect(S_OK, hRes); | 
|  | written -= sizeof(header); | 
|  | expect(0, written); | 
|  |  | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | dpa = NULL; | 
|  | hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, NULL); | 
|  | expect(S_OK, hRes); | 
|  | DPA_Destroy(dpa); | 
|  |  | 
|  | /* try with altered dwData2 field */ | 
|  | header.dwSize = sizeof(header); | 
|  | header.dwData2 = 2; | 
|  | header.dwItems = 0; | 
|  |  | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  | hRes = IStream_Write(pStm, &header, sizeof(header), &written); | 
|  | expect(S_OK, hRes); | 
|  | written -= sizeof(header); | 
|  | expect(0, written); | 
|  |  | 
|  | li.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, (void*)0xdeadbeef); | 
|  | expect(E_FAIL, hRes); | 
|  |  | 
|  | ret = IStream_Release(pStm); | 
|  | ok(!ret, "ret=%d\n", ret); | 
|  |  | 
|  | ret = IStorage_Release(pStg); | 
|  | ok(!ret, "ret=%d\n", ret); | 
|  |  | 
|  | CoUninitialize(); | 
|  | } | 
|  |  | 
|  | static void test_DPA_SaveStream(void) | 
|  | { | 
|  | HDPA dpa; | 
|  | static const WCHAR szStg[] = { 'S','t','g',0 }; | 
|  | IStorage* pStg = NULL; | 
|  | IStream* pStm = NULL; | 
|  | DWORD dwMode, dw; | 
|  | HRESULT hRes; | 
|  | ULONG ret; | 
|  | INT i; | 
|  | BOOL rc; | 
|  | LARGE_INTEGER liZero; | 
|  |  | 
|  | if(!pDPA_SaveStream) | 
|  | { | 
|  | win_skip("DPA_SaveStream() not available. Skipping stream tests.\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | hRes = CoInitialize(NULL); | 
|  | if (hRes != S_OK) | 
|  | { | 
|  | ok(0, "hResult: %d\n", hRes); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dwMode = STGM_DIRECT|STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE; | 
|  | hRes = StgCreateDocfile(NULL, dwMode|STGM_DELETEONRELEASE, 0, &pStg); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = IStorage_CreateStream(pStg, szStg, dwMode, 0, 0, &pStm); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | dpa = pDPA_Create(0); | 
|  |  | 
|  | /* simple parameter check */ | 
|  | hRes = pDPA_SaveStream(dpa, NULL, pStm, NULL); | 
|  | ok(hRes == E_INVALIDARG || | 
|  | broken(hRes == S_OK) /* XP and below */, "Wrong result, %d\n", hRes); | 
|  | if (0) { | 
|  | /* crashes on XP */ | 
|  | hRes = pDPA_SaveStream(NULL, CB_Save, pStm, NULL); | 
|  | expect(E_INVALIDARG, hRes); | 
|  |  | 
|  | hRes = pDPA_SaveStream(dpa, CB_Save, NULL, NULL); | 
|  | expect(E_INVALIDARG, hRes); | 
|  | } | 
|  |  | 
|  | /* saving/loading */ | 
|  | for (i = 0; i < 6; i++) | 
|  | { | 
|  | ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1)); | 
|  | ok(ret == i, "ret=%d\n", ret); | 
|  | } | 
|  |  | 
|  | liZero.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, liZero, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  |  | 
|  | hRes = pDPA_SaveStream(dpa, CB_Save, pStm, (void*)0xdeadbeef); | 
|  | expect(S_OK, hRes); | 
|  | pDPA_Destroy(dpa); | 
|  |  | 
|  | liZero.QuadPart = 0; | 
|  | hRes = IStream_Seek(pStm, liZero, STREAM_SEEK_SET, NULL); | 
|  | expect(S_OK, hRes); | 
|  | hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, (void*)0xdeadbeef); | 
|  | expect(S_OK, hRes); | 
|  | rc = CheckDPA(dpa, 0x123456, &dw); | 
|  | ok(rc, "dw=0x%x\n", dw); | 
|  | pDPA_Destroy(dpa); | 
|  |  | 
|  | ret = IStream_Release(pStm); | 
|  | ok(!ret, "ret=%d\n", ret); | 
|  |  | 
|  | ret = IStorage_Release(pStg); | 
|  | ok(!ret, "ret=%d\n", ret); | 
|  |  | 
|  | CoUninitialize(); | 
|  | } | 
|  |  | 
|  | START_TEST(dpa) | 
|  | { | 
|  | HMODULE hcomctl32; | 
|  |  | 
|  | hcomctl32 = GetModuleHandleA("comctl32.dll"); | 
|  |  | 
|  | if(!InitFunctionPtrs(hcomctl32)) | 
|  | { | 
|  | win_skip("Needed functions are not available\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | test_dpa(); | 
|  | test_DPA_Merge(); | 
|  | test_DPA_EnumCallback(); | 
|  | test_DPA_DestroyCallback(); | 
|  | test_DPA_LoadStream(); | 
|  | test_DPA_SaveStream(); | 
|  | } |