Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605/* * 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.exestatic HANDLE s_hThread = NULL;static INT s_iStage = -1, s_iStep = -1;static BYTE s_abChecks[NUM_CHECKS] = { 0 }; // Flags for testingstatic 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 = 0; 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_UNREACHABLE; }
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_COPYDATAstatic 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 CALLBACKMainWndProc(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));}