mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 02:25:17 +00:00
75cc5b2b1c
and delete obsolete "iexplore.exe" tests. CORE-13950
608 lines
18 KiB
C++
608 lines
18 KiB
C++
/*
|
|
* PROJECT: ReactOS api tests
|
|
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
|
|
* PURPOSE: Test for SHChangeNotify
|
|
* COPYRIGHT: Copyright 2020-2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
|
|
*/
|
|
|
|
// NOTE: This testcase requires shell32_apitest_sub.exe.
|
|
|
|
#include "shelltest.h"
|
|
#include "shell32_apitest_sub.h"
|
|
#include <assert.h>
|
|
|
|
#define NUM_STEP 8
|
|
#define NUM_CHECKS 12
|
|
#define INTERVAL 0
|
|
#define MAX_EVENT_TYPE 6
|
|
|
|
static HWND s_hMainWnd = NULL, s_hSubWnd = NULL;
|
|
static WCHAR s_szSubProgram[MAX_PATH]; // shell32_apitest_sub.exe
|
|
static HANDLE s_hThread = NULL;
|
|
static INT s_iStage = -1, s_iStep = -1;
|
|
static BYTE s_abChecks[NUM_CHECKS] = { 0 }; // Flags for testing
|
|
static BOOL s_bGotUpdateDir = FALSE; // Got SHCNE_UPDATEDIR?
|
|
|
|
static BOOL DoCreateFile(LPCWSTR pszFileName)
|
|
{
|
|
HANDLE hFile = ::CreateFileW(pszFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
::CloseHandle(hFile);
|
|
return hFile != INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
static void DoDeleteDirectory(LPCWSTR pszDir)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
ZeroMemory(szPath, sizeof(szPath));
|
|
StringCchCopyW(szPath, _countof(szPath), pszDir); // Double-NULL terminated
|
|
SHFILEOPSTRUCTW FileOp = { NULL, FO_DELETE, szPath, NULL, FOF_NOCONFIRMATION | FOF_SILENT };
|
|
SHFileOperation(&FileOp);
|
|
}
|
|
|
|
static INT GetEventType(LONG lEvent)
|
|
{
|
|
switch (lEvent)
|
|
{
|
|
case SHCNE_CREATE: return 0;
|
|
case SHCNE_DELETE: return 1;
|
|
case SHCNE_RENAMEITEM: return 2;
|
|
case SHCNE_MKDIR: return 3;
|
|
case SHCNE_RMDIR: return 4;
|
|
case SHCNE_RENAMEFOLDER: return 5;
|
|
C_ASSERT(5 + 1 == MAX_EVENT_TYPE);
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
#define FILE_1 L"_TESTFILE_1_.txt"
|
|
#define FILE_2 L"_TESTFILE_2_.txt"
|
|
#define DIR_1 L"_TESTDIR_1_"
|
|
#define DIR_2 L"_TESTDIR_2_"
|
|
|
|
static WCHAR s_szDir1[MAX_PATH];
|
|
static WCHAR s_szDir1InDir1[MAX_PATH];
|
|
static WCHAR s_szDir2InDir1[MAX_PATH];
|
|
static WCHAR s_szFile1InDir1InDir1[MAX_PATH];
|
|
static WCHAR s_szFile1InDir1[MAX_PATH];
|
|
static WCHAR s_szFile2InDir1[MAX_PATH];
|
|
|
|
static void DoDeleteFilesAndDirs(void)
|
|
{
|
|
::DeleteFileW(s_szFile1InDir1);
|
|
::DeleteFileW(s_szFile2InDir1);
|
|
::DeleteFileW(s_szFile1InDir1InDir1);
|
|
DoDeleteDirectory(s_szDir1InDir1);
|
|
DoDeleteDirectory(s_szDir2InDir1);
|
|
DoDeleteDirectory(s_szDir1);
|
|
}
|
|
|
|
static void TEST_Quit(void)
|
|
{
|
|
CloseHandle(s_hThread);
|
|
s_hThread = NULL;
|
|
|
|
PostMessageW(s_hSubWnd, WM_COMMAND, IDNO, 0); // Finish
|
|
DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, TRUE, TRUE); // Close sub-windows
|
|
|
|
DoDeleteFilesAndDirs();
|
|
}
|
|
|
|
static void DoBuildFilesAndDirs(void)
|
|
{
|
|
WCHAR szPath1[MAX_PATH];
|
|
SHGetSpecialFolderPathW(NULL, szPath1, CSIDL_PERSONAL, FALSE); // My Documents
|
|
PathAppendW(szPath1, DIR_1);
|
|
StringCchCopyW(s_szDir1, _countof(s_szDir1), szPath1);
|
|
|
|
PathAppendW(szPath1, DIR_1);
|
|
StringCchCopyW(s_szDir1InDir1, _countof(s_szDir1InDir1), szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
|
|
PathAppendW(szPath1, DIR_2);
|
|
StringCchCopyW(s_szDir2InDir1, _countof(s_szDir2InDir1), szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
|
|
PathAppendW(szPath1, DIR_1);
|
|
PathAppendW(szPath1, FILE_1);
|
|
StringCchCopyW(s_szFile1InDir1InDir1, _countof(s_szFile1InDir1InDir1), szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
|
|
PathAppendW(szPath1, FILE_1);
|
|
StringCchCopyW(s_szFile1InDir1, _countof(s_szFile1InDir1), szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
|
|
PathAppendW(szPath1, FILE_2);
|
|
StringCchCopyW(s_szFile2InDir1, _countof(s_szFile2InDir1), szPath1);
|
|
PathRemoveFileSpecW(szPath1);
|
|
|
|
#define TRACE_PATH(path) trace(#path ": %ls\n", path)
|
|
TRACE_PATH(s_szDir1);
|
|
TRACE_PATH(s_szDir1InDir1);
|
|
TRACE_PATH(s_szFile1InDir1);
|
|
TRACE_PATH(s_szFile1InDir1InDir1);
|
|
TRACE_PATH(s_szFile2InDir1);
|
|
#undef TRACE_PATH
|
|
|
|
DoDeleteFilesAndDirs();
|
|
|
|
::CreateDirectoryW(s_szDir1, NULL);
|
|
ok_int(!!PathIsDirectoryW(s_szDir1), TRUE);
|
|
|
|
DoDeleteDirectory(s_szDir1InDir1);
|
|
ok_int(!PathIsDirectoryW(s_szDir1InDir1), TRUE);
|
|
}
|
|
|
|
static void DoTestEntry(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
|
|
|
|
szPath1[0] = szPath2[0] = 0;
|
|
SHGetPathFromIDListW(pidl1, szPath1);
|
|
SHGetPathFromIDListW(pidl2, szPath2);
|
|
|
|
trace("(0x%lX, '%ls', '%ls')\n", lEvent, szPath1, szPath2);
|
|
|
|
if (lEvent == SHCNE_UPDATEDIR)
|
|
{
|
|
trace("Got SHCNE_UPDATEDIR\n");
|
|
s_bGotUpdateDir = TRUE;
|
|
return;
|
|
}
|
|
|
|
INT iEventType = GetEventType(lEvent);
|
|
if (iEventType < 0)
|
|
return;
|
|
|
|
assert(iEventType < MAX_EVENT_TYPE);
|
|
|
|
INT i = 0;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, L"") == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szDir2InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile1InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile2InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath1, s_szFile1InDir1InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, L"") == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szDir2InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile1InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile2InDir1) == 0) << iEventType;
|
|
s_abChecks[i++] |= (lstrcmpiW(szPath2, s_szFile1InDir1InDir1) == 0) << iEventType;
|
|
assert(i == NUM_CHECKS);
|
|
}
|
|
|
|
static LPCSTR StringFromChecks(void)
|
|
{
|
|
static char s_sz[2 * NUM_CHECKS + 1];
|
|
|
|
char *pch = s_sz;
|
|
for (INT i = 0; i < NUM_CHECKS; ++i)
|
|
{
|
|
WCHAR sz[3];
|
|
StringCchPrintfW(sz, _countof(sz), L"%02X", s_abChecks[i]);
|
|
*pch++ = sz[0];
|
|
*pch++ = sz[1];
|
|
}
|
|
|
|
assert((pch - s_sz) + 1 == sizeof(s_sz));
|
|
|
|
*pch = 0;
|
|
return s_sz;
|
|
}
|
|
|
|
struct TEST_ANSWER
|
|
{
|
|
INT lineno;
|
|
LPCSTR answer;
|
|
};
|
|
|
|
static void DoStepCheck(INT iStage, INT iStep, LPCSTR checks)
|
|
{
|
|
assert(0 <= iStep);
|
|
assert(iStep < NUM_STEP);
|
|
|
|
assert(0 <= iStage);
|
|
assert(iStage < NUM_STAGE);
|
|
|
|
if (s_bGotUpdateDir)
|
|
{
|
|
ok_int(TRUE, TRUE);
|
|
return;
|
|
}
|
|
|
|
LPCSTR answer = NULL;
|
|
INT lineno;
|
|
switch (iStage)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 3:
|
|
case 6:
|
|
case 9:
|
|
{
|
|
static const TEST_ANSWER c_answers[] =
|
|
{
|
|
{ __LINE__, "000000010000010000000000" }, // 0
|
|
{ __LINE__, "000000040000000000000400" }, // 1
|
|
{ __LINE__, "000000000200020000000000" }, // 2
|
|
{ __LINE__, "000000000000080000000000" }, // 3
|
|
{ __LINE__, "000000000001010000000000" }, // 4
|
|
{ __LINE__, "000000000002020000000000" }, // 5
|
|
{ __LINE__, "000000000000000020000000" }, // 6
|
|
{ __LINE__, "000010000000100000000000" }, // 7
|
|
};
|
|
C_ASSERT(_countof(c_answers) == NUM_STEP);
|
|
lineno = c_answers[iStep].lineno;
|
|
answer = c_answers[iStep].answer;
|
|
break;
|
|
}
|
|
case 2:
|
|
case 4:
|
|
case 5:
|
|
case 7:
|
|
{
|
|
static const TEST_ANSWER c_answers[] =
|
|
{
|
|
{ __LINE__, "000000000000000000000000" }, // 0
|
|
{ __LINE__, "000000000000000000000000" }, // 1
|
|
{ __LINE__, "000000000000000000000000" }, // 2
|
|
{ __LINE__, "000000000000000000000000" }, // 3
|
|
{ __LINE__, "000000000000000000000000" }, // 4
|
|
{ __LINE__, "000000000000000000000000" }, // 5
|
|
{ __LINE__, "000000000000000000000000" }, // 6
|
|
{ __LINE__, "000000000000000000000000" }, // 7
|
|
};
|
|
C_ASSERT(_countof(c_answers) == NUM_STEP);
|
|
lineno = c_answers[iStep].lineno;
|
|
answer = c_answers[iStep].answer;
|
|
break;
|
|
}
|
|
case 8:
|
|
{
|
|
static const TEST_ANSWER c_answers[] =
|
|
{
|
|
{ __LINE__, "000000010000010000000000" }, // 0
|
|
{ __LINE__, "000000040000000000000400" }, // 1
|
|
{ __LINE__, "000000000200020000000000" }, // 2
|
|
{ __LINE__, "000000000000080000000000" }, // 3
|
|
{ __LINE__, "000000000001010000000000" }, // 4 // Recursive case
|
|
{ __LINE__, "000000000002020000000000" }, // 5 // Recursive case
|
|
{ __LINE__, "000000000000000020000000" }, // 6
|
|
{ __LINE__, "000010000000100000000000" }, // 7
|
|
};
|
|
C_ASSERT(_countof(c_answers) == NUM_STEP);
|
|
lineno = c_answers[iStep].lineno;
|
|
answer = c_answers[iStep].answer;
|
|
if (iStep == 4 || iStep == 5) // Recursive cases
|
|
{
|
|
if (lstrcmpA(checks, "000000000000000000000000") == 0)
|
|
{
|
|
trace("Warning! Recursive cases...\n");
|
|
answer = "000000000000000000000000";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ok(lstrcmpA(checks, answer) == 0,
|
|
"Line %d: '%s' vs '%s' at Stage %d, Step %d\n", lineno, checks, answer, iStage, iStep);
|
|
}
|
|
|
|
static DWORD WINAPI StageThreadFunc(LPVOID arg)
|
|
{
|
|
BOOL ret;
|
|
|
|
trace("Stage %d\n", s_iStage);
|
|
|
|
// 0: Create file1 in dir1
|
|
s_iStep = 0;
|
|
trace("Step %d\n", s_iStep);
|
|
SHChangeNotify(0, SHCNF_PATHW | SHCNF_FLUSH, NULL, NULL);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = DoCreateFile(s_szFile1InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, 0);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 1: Rename file1 as file2 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = MoveFileW(s_szFile1InDir1, s_szFile2InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1, s_szFile2InDir1);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 2: Delete file2 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = DeleteFileW(s_szFile2InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile2InDir1, NULL);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 3: Create dir1 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = CreateDirectoryExW(s_szDir1, s_szDir1InDir1, NULL);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, NULL);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 4: Create file1 in dir1 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = DoCreateFile(s_szFile1InDir1InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 5: Delete file1 in dir1 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = DeleteFileW(s_szFile1InDir1InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, s_szFile1InDir1InDir1, NULL);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 6: Rename dir1 as dir2 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = ::MoveFileW(s_szDir1InDir1, s_szDir2InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATHW | SHCNF_FLUSH, s_szDir1InDir1, s_szDir2InDir1);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 7: Remove dir2 in dir1
|
|
++s_iStep;
|
|
trace("Step %d\n", s_iStep);
|
|
ZeroMemory(s_abChecks, sizeof(s_abChecks));
|
|
ret = RemoveDirectoryW(s_szDir2InDir1);
|
|
ok_int(ret, TRUE);
|
|
SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW | SHCNF_FLUSH, s_szDir2InDir1, NULL);
|
|
::Sleep(INTERVAL);
|
|
DoStepCheck(s_iStage, s_iStep, StringFromChecks());
|
|
|
|
// 8: Finish
|
|
++s_iStep;
|
|
assert(s_iStep == NUM_STEP);
|
|
C_ASSERT(NUM_STEP == 8);
|
|
if (s_iStage + 1 < NUM_STAGE)
|
|
{
|
|
::PostMessage(s_hSubWnd, WM_COMMAND, IDRETRY, 0); // Next stage
|
|
}
|
|
else
|
|
{
|
|
// Finish
|
|
::PostMessage(s_hSubWnd, WM_COMMAND, IDNO, 0);
|
|
::PostMessage(s_hMainWnd, WM_COMMAND, IDNO, 0);
|
|
}
|
|
|
|
s_iStep = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// WM_COPYDATA
|
|
static BOOL OnCopyData(HWND hwnd, HWND hwndSender, COPYDATASTRUCT *pCopyData)
|
|
{
|
|
if (pCopyData->dwData != 0xBEEFCAFE)
|
|
return FALSE;
|
|
|
|
LPBYTE pbData = (LPBYTE)pCopyData->lpData;
|
|
LPBYTE pb = pbData;
|
|
|
|
LONG cbTotal = pCopyData->cbData;
|
|
assert(cbTotal >= LONG(sizeof(LONG) + sizeof(DWORD) + sizeof(DWORD)));
|
|
|
|
LONG lEvent = *(LONG*)pb;
|
|
pb += sizeof(lEvent);
|
|
|
|
DWORD cbPidl1 = *(DWORD*)pb;
|
|
pb += sizeof(cbPidl1);
|
|
|
|
DWORD cbPidl2 = *(DWORD*)pb;
|
|
pb += sizeof(cbPidl2);
|
|
|
|
LPITEMIDLIST pidl1 = NULL;
|
|
if (cbPidl1)
|
|
{
|
|
pidl1 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl1);
|
|
CopyMemory(pidl1, pb, cbPidl1);
|
|
pb += cbPidl1;
|
|
}
|
|
|
|
LPITEMIDLIST pidl2 = NULL;
|
|
if (cbPidl2)
|
|
{
|
|
pidl2 = (LPITEMIDLIST)CoTaskMemAlloc(cbPidl2);
|
|
CopyMemory(pidl2, pb, cbPidl2);
|
|
pb += cbPidl2;
|
|
}
|
|
|
|
assert((pb - pbData) == cbTotal);
|
|
|
|
DoTestEntry(lEvent, pidl1, pidl2);
|
|
|
|
CoTaskMemFree(pidl1);
|
|
CoTaskMemFree(pidl2);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static LRESULT CALLBACK
|
|
MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
s_hMainWnd = hwnd;
|
|
return 0;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDYES: // Start testing
|
|
{
|
|
s_iStage = 0;
|
|
s_bGotUpdateDir = FALSE;
|
|
s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL);
|
|
if (!s_hThread)
|
|
{
|
|
skip("!s_hThread\n");
|
|
DestroyWindow(hwnd);
|
|
}
|
|
break;
|
|
}
|
|
case IDRETRY: // New stage
|
|
{
|
|
::CloseHandle(s_hThread);
|
|
++s_iStage;
|
|
s_bGotUpdateDir = FALSE;
|
|
s_hThread = ::CreateThread(NULL, 0, StageThreadFunc, hwnd, 0, NULL);
|
|
if (!s_hThread)
|
|
{
|
|
skip("!s_hThread\n");
|
|
DestroyWindow(hwnd);
|
|
}
|
|
break;
|
|
}
|
|
case IDNO: // Quit
|
|
{
|
|
s_iStage = -1;
|
|
DestroyWindow(hwnd);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_COPYDATA:
|
|
if (s_iStage < 0 || s_iStep < 0)
|
|
break;
|
|
|
|
OnCopyData(hwnd, (HWND)wParam, (COPYDATASTRUCT*)lParam);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
::PostQuitMessage(0);
|
|
break;
|
|
|
|
default:
|
|
return ::DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static BOOL TEST_Init(void)
|
|
{
|
|
if (!FindSubProgram(s_szSubProgram, _countof(s_szSubProgram)))
|
|
{
|
|
skip("shell32_apitest_sub.exe not found\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// close the SUB_CLASSNAME windows
|
|
DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, TRUE, TRUE);
|
|
|
|
// Execute sub program
|
|
HINSTANCE hinst = ShellExecuteW(NULL, NULL, s_szSubProgram, L"---", NULL, SW_SHOWNORMAL);
|
|
if ((INT_PTR)hinst <= 32)
|
|
{
|
|
skip("Unable to run shell32_apitest_sub.exe.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// prepare for files and dirs
|
|
DoBuildFilesAndDirs();
|
|
|
|
// Register main window
|
|
WNDCLASSW wc = { 0, MainWndProc };
|
|
wc.hInstance = GetModuleHandleW(NULL);
|
|
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
|
|
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
|
|
wc.lpszClassName = MAIN_CLASSNAME;
|
|
if (!RegisterClassW(&wc))
|
|
{
|
|
skip("RegisterClassW failed\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Create main window
|
|
HWND hwnd = CreateWindowW(MAIN_CLASSNAME, MAIN_CLASSNAME, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, 400, 100,
|
|
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
|
if (!hwnd)
|
|
{
|
|
skip("CreateWindowW failed\n");
|
|
return FALSE;
|
|
}
|
|
::ShowWindow(hwnd, SW_SHOWNORMAL);
|
|
::UpdateWindow(hwnd);
|
|
|
|
// Find sub-window
|
|
s_hSubWnd = DoWaitForWindow(SUB_CLASSNAME, SUB_CLASSNAME, FALSE, FALSE);
|
|
if (!s_hSubWnd)
|
|
{
|
|
skip("Unable to find sub-program window.\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Start testing
|
|
SendMessageW(s_hSubWnd, WM_COMMAND, IDYES, 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void TEST_Main(void)
|
|
{
|
|
if (!TEST_Init())
|
|
{
|
|
skip("Unable to start testing.\n");
|
|
TEST_Quit();
|
|
return;
|
|
}
|
|
|
|
// Message loop
|
|
MSG msg;
|
|
while (GetMessageW(&msg, NULL, 0, 0))
|
|
{
|
|
::TranslateMessage(&msg);
|
|
::DispatchMessage(&msg);
|
|
}
|
|
|
|
TEST_Quit();
|
|
}
|
|
|
|
START_TEST(SHChangeNotify)
|
|
{
|
|
trace("Please close all Explorer windows before testing.\n");
|
|
trace("Please don't operate your PC while testing.\n");
|
|
|
|
DWORD dwOldTick = GetTickCount();
|
|
TEST_Main();
|
|
DWORD dwNewTick = GetTickCount();
|
|
|
|
DWORD dwTick = dwNewTick - dwOldTick;
|
|
trace("SHChangeNotify: Total %lu.%lu sec\n", (dwTick / 1000), (dwTick / 100 % 10));
|
|
}
|