| /* |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 DPAM_NOSORT 0x1 |
| #define DPAM_INSERT 0x4 |
| #define DPAM_DELETE 0x8 |
| |
| typedef struct _ITEMDATA |
| { |
| INT iPos; |
| PVOID pvData; |
| } ITEMDATA, *LPITEMDATA; |
| |
| typedef PVOID (CALLBACK *PFNDPAMERGE)(UINT,PVOID,PVOID,LPARAM); |
| typedef HRESULT (CALLBACK *PFNDPASTM)(LPITEMDATA,IStream*,LPARAM); |
| |
| 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*,PFNDPASTM,IStream*,LPARAM); |
| static BOOL (WINAPI *pDPA_Merge)(const HDPA,const HDPA,DWORD,PFNDPACOMPARE,PFNDPAMERGE,LPARAM); |
| static HRESULT (WINAPI *pDPA_SaveStream)(HDPA,PFNDPASTM,IStream*,LPARAM); |
| 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; |
| } |
| |
| static PVOID CALLBACK CB_MergeInsertSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp) |
| { |
| ok(lp == 0xdeadbeef, "lp=%ld\n", lp); |
| return p1; |
| } |
| |
| static PVOID CALLBACK CB_MergeDeleteOddSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp) |
| { |
| 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(LPITEMDATA pInfo, IStream *pStm, LPARAM lp) |
| { |
| HRESULT hRes; |
| |
| ok(lp == 0xdeadbeef, "lp=%ld\n", lp); |
| hRes = IStream_Write(pStm, &pInfo->iPos, sizeof(INT), NULL); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| hRes = IStream_Write(pStm, &pInfo->pvData, sizeof(PVOID), NULL); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| return S_OK; |
| } |
| |
| static HRESULT CALLBACK CB_Load(LPITEMDATA pInfo, IStream *pStm, LPARAM lp) |
| { |
| HRESULT hRes; |
| INT iOldPos; |
| |
| iOldPos = pInfo->iPos; |
| ok(lp == 0xdeadbeef, "lp=%ld\n", lp); |
| hRes = IStream_Read(pStm, &pInfo->iPos, sizeof(INT), NULL); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| ok(pInfo->iPos == iOldPos, "iPos=%d iOldPos=%d\n", pInfo->iPos, iOldPos); |
| hRes = IStream_Read(pStm, &pInfo->pvData, sizeof(PVOID), NULL); |
| ok(hRes == S_OK, "hRes=0x%lx\n", 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)(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; |
| HRESULT hRes; |
| |
| GetSystemInfo(&si); |
| hHeap = HeapCreate(0, 1, 2); |
| ok(hHeap != NULL, "error=%ld\n", GetLastError()); |
| dpa3 = pDPA_CreateEx(0, hHeap); |
| ok(dpa3 != NULL, "\n"); |
| ret = pDPA_Grow(dpa3, si.dwPageSize + 1); |
| todo_wine ok(!ret && GetLastError() == ERROR_NOT_ENOUGH_MEMORY, |
| "ret=%d error=%ld\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 greated gap */ |
| ok(pDPA_SetPtr(dpa, 0, (PVOID)5), "\n"); |
| ok(CheckDPA(dpa, 0x56, &dw), "dw=0x%lx\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); |
| |
| ok(CheckDPA(dpa, 0x516324, &dw), "dw=0x%lx\n", dw); |
| |
| for(i = 1; i <= 6; i++) |
| { |
| INT j, k; |
| k = pDPA_GetPtrIndex(dpa, (PVOID)i); |
| /* Linear searches should work on unsorted DPAs */ |
| j = pDPA_Search(dpa, (PVOID)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"); |
| ok(CheckDPA(dpa, 0x654321, &dw), "dw=0x%lx\n", dw); |
| |
| /* Clone into a new DPA */ |
| dpa2 = pDPA_Clone(dpa, NULL); |
| ok(dpa2 != NULL, "\n"); |
| /* The old data should have been preserved */ |
| ok(CheckDPA(dpa2, 0x654321, &dw2), "dw=0x%lx\n", dw2); |
| ok(pDPA_Sort(dpa, CB_CmpLT, 0xdeadbeef), "\n"); |
| |
| /* Test if the DPA itself was really copied */ |
| ok(CheckDPA(dpa, 0x123456, &dw), "dw=0x%lx\n", dw ); |
| ok(CheckDPA(dpa2, 0x654321, &dw2), "dw2=0x%lx\n", dw2); |
| |
| /* Clone into an old DPA */ |
| p = NULL; SetLastError(ERROR_SUCCESS); |
| p = pDPA_Clone(dpa, dpa3); |
| ok(p == dpa3, "p=%p\n", p); |
| ok(CheckDPA(dpa3, 0x123456, &dw3), "dw3=0x%lx\n", dw3); |
| |
| for(i = 1; i <= 6; i++) |
| { |
| INT j; |
| |
| /* The array is in order so ptr == index+1 */ |
| j = pDPA_GetPtrIndex(dpa, (PVOID)i); |
| ok(j+1 == i, "j=%d i=%d\n", j, i); |
| j = pDPA_Search(dpa, (PVOID)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)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)i, i+1, CB_CmpLT, 0xdeadbeef, DPAS_SORTED); |
| todo_wine 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); |
| ok(CheckDPA(dpa, 0x12456, &dw), "dw=0x%lx\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); |
| ok(CheckDPA(dpa, 0x123456, &dw), "dw=0x%lx\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)i, 0, CB_CmpGT, 0xdeadbeef, |
| DPAS_SORTED|DPAS_INSERTBEFORE); |
| ok(j != i, "i=%d\n", i); |
| } |
| |
| if(pDPA_Merge) |
| { |
| /* Delete all even entries from dpa */ |
| p = pDPA_DeletePtr(dpa, 1); |
| p = pDPA_DeletePtr(dpa, 2); |
| p = pDPA_DeletePtr(dpa, 3); |
| ok(CheckDPA(dpa, 0x135, &dw), "dw=0x%lx\n", dw); |
| |
| /* Delete all odd entries from dpa2 */ |
| pDPA_Merge(dpa2, dpa, DPAM_DELETE, |
| CB_CmpLT, CB_MergeDeleteOddSrc, 0xdeadbeef); |
| todo_wine ok(CheckDPA(dpa2, 0x246, &dw2), "dw=0x%lx\n", dw2); |
| |
| /* Merge dpa3 into dpa2 and dpa */ |
| pDPA_Merge(dpa, dpa3, DPAM_INSERT|DPAM_NOSORT, |
| CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); |
| pDPA_Merge(dpa2, dpa3, DPAM_INSERT|DPAM_NOSORT, |
| CB_CmpLT, CB_MergeInsertSrc, 0xdeadbeef); |
| |
| ok(CheckDPA(dpa, 0x123456, &dw ), "dw=0x%lx\n", dw); |
| ok(CheckDPA(dpa2, 0x123456, &dw2), "dw2=0x%lx\n", dw2); |
| ok(CheckDPA(dpa3, 0x123456, &dw3), "dw3=0x%lx\n", dw3); |
| } |
| |
| if(pDPA_EnumCallback) |
| { |
| nEnum = 0; |
| pDPA_EnumCallback(dpa2, CB_EnumFirstThree, (PVOID)dpa2); |
| ok(CheckDPA(dpa2, 0x777456, &dw2), "dw=0x%lx\n", dw2); |
| ok(nEnum == 3, "nEnum=%d\n", nEnum); |
| } |
| |
| /* 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); |
| ok(CheckDPA(dpa2, 0, &dw2), "dw2=0x%lx\n", dw2); |
| pDPA_Destroy(dpa2); |
| |
| if(pDPA_DestroyCallback) |
| { |
| nEnum = 0; |
| pDPA_DestroyCallback(dpa3, CB_EnumFirstThree, dpa3); |
| ok(nEnum == 3, "nEnum=%d\n", nEnum); |
| } |
| else pDPA_Destroy(dpa3); |
| |
| if(!pDPA_SaveStream) |
| goto skip_stream_tests; |
| |
| hRes = CoInitialize(NULL); |
| if(hRes == S_OK) |
| { |
| static const WCHAR szStg[] = { 'S','t','g',0 }; |
| IStorage* pStg = NULL; |
| IStream* pStm = NULL; |
| LARGE_INTEGER liZero; |
| DWORD dwMode; |
| liZero.QuadPart = 0; |
| |
| dwMode = STGM_DIRECT|STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE; |
| hRes = StgCreateDocfile(NULL, dwMode|STGM_DELETEONRELEASE, 0, &pStg); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| |
| hRes = IStorage_CreateStream(pStg, szStg, dwMode, 0, 0, &pStm); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| |
| hRes = pDPA_SaveStream(dpa, CB_Save, pStm, 0xdeadbeef); |
| todo_wine ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| pDPA_Destroy(dpa); |
| |
| hRes = IStream_Seek(pStm, liZero, STREAM_SEEK_SET, NULL); |
| ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, 0xdeadbeef); |
| todo_wine ok(hRes == S_OK, "hRes=0x%lx\n", hRes); |
| todo_wine ok(CheckDPA(dpa, 0x123456, &dw), "dw=0x%lx\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(); |
| } |
| else ok(0, "hResult: %ld\n", hRes); |
| |
| skip_stream_tests: |
| pDPA_Destroy(dpa); |
| } |
| |
| START_TEST(dpa) |
| { |
| HMODULE hcomctl32; |
| |
| hcomctl32 = GetModuleHandleA("comctl32.dll"); |
| |
| if(!hcomctl32) |
| { |
| ok(0, "error=%ld\n", GetLastError()); |
| return; |
| } |
| |
| if(InitFunctionPtrs(hcomctl32)) |
| test_dpa(); |
| else |
| trace("skipping tests\n"); |
| |
| FreeLibrary(hcomctl32); |
| } |