/*
 * 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-2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
 */

// NOTE: This test program closes the Explorer windows before tests.

#include "shelltest.h"
#include "shell32_apitest_sub.h"
#include <time.h>
#include <process.h>
#include <versionhelpers.h>

// --- The selection of tests ---
#define DISABLE_THIS_TESTCASE
#define NO_TRIVIAL
//#define NO_INTERRUPT_LEVEL
//#define NO_SHELL_LEVEL
#define NEW_DELIVERY_ONLY
//#define RANDOM_HALF
#define RANDOM_QUARTER

// --- Show the elapsed time by GetTickCount() ---
//#define ENTRY_TICK
#define GROUP_TICK
#define TOTAL_TICK

static HWND s_hwnd = NULL;
static WCHAR s_szSubProgram[MAX_PATH];
static HANDLE s_hThread = NULL;
static HANDLE s_hEvent = NULL;

static BOOL DoCreateEmptyFile(LPCWSTR pszFileName)
{
    FILE *fp = _wfopen(pszFileName, L"wb");
    if (fp)
        fclose(fp);
    return fp != NULL;
}

struct TEST_ENTRY;

typedef BOOL (*ACTION)(const struct TEST_ENTRY *entry);

typedef struct TEST_ENTRY
{
    INT line;
    DIRTYPE iWriteDir;
    LPCSTR pattern;
    LPCWSTR path1;
    LPCWSTR path2;
    ACTION action;
} TEST_ENTRY;

#define TEST_FILE      L"_TEST_.txt"
#define TEST_FILE_KAI  L"_TEST_KAI_.txt"
#define TEST_DIR       L"_TESTDIR_"
#define TEST_DIR_KAI   L"_TESTDIR_KAI_"
#define MOVE_FILE(from, to) MoveFileW((from), (to))

static BOOL DoAction1(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_FILE);
    ok(DoCreateEmptyFile(pszPath), "Line %d: DoCreateEmptyFile failed\n", entry->line);
    return TRUE;
}

static BOOL DoAction2(const TEST_ENTRY *entry)
{
    LPWSTR pszPath1 = DoGetDir(entry->iWriteDir), pszPath2 = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath1, TEST_FILE);
    PathAppendW(pszPath2, TEST_FILE_KAI);
    ok(MOVE_FILE(pszPath1, pszPath2), "Line %d: MOVE_FILE(%ls, %ls) failed (%ld)\n",
       entry->line, pszPath1, pszPath2, GetLastError());
    return TRUE;
}

static BOOL DoAction3(const TEST_ENTRY *entry)
{
    LPWSTR pszPath1 = DoGetDir(entry->iWriteDir), pszPath2 = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath1, TEST_FILE_KAI);
    PathAppendW(pszPath2, TEST_FILE);
    ok(MOVE_FILE(pszPath1, pszPath2), "Line %d: MOVE_FILE(%ls, %ls) failed (%ld)\n",
       entry->line, pszPath1, pszPath2, GetLastError());
    return TRUE;
}

static BOOL DoAction4(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_FILE);
    ok(DeleteFileW(pszPath), "Line %d: DeleteFileW(%ls) failed (%ld)\n",
       entry->line, pszPath, GetLastError());
    return TRUE;
}

static BOOL DoAction5(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_DIR);
    ok(CreateDirectoryW(pszPath, NULL), "Line %d: CreateDirectoryW(%ls) failed (%ld)\n",
       entry->line, pszPath, GetLastError());
    return TRUE;
}

static BOOL DoAction6(const TEST_ENTRY *entry)
{
    LPWSTR pszPath1 = DoGetDir(entry->iWriteDir), pszPath2 = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath1, TEST_DIR);
    PathAppendW(pszPath2, TEST_DIR_KAI);
    ok(MOVE_FILE(pszPath1, pszPath2), "Line %d: MOVE_FILE(%ls, %ls) failed (%ld)\n",
       entry->line, pszPath1, pszPath2, GetLastError());
    return TRUE;
}

static BOOL DoAction7(const TEST_ENTRY *entry)
{
    LPWSTR pszPath1 = DoGetDir(entry->iWriteDir), pszPath2 = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath1, TEST_DIR_KAI);
    PathAppendW(pszPath2, TEST_DIR);
    ok(MOVE_FILE(pszPath1, pszPath2), "Line %d: MOVE_FILE(%ls, %ls) failed (%ld)\n",
       entry->line, pszPath1, pszPath2, GetLastError());
    return TRUE;
}

static BOOL DoAction8(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_DIR);
    ok(RemoveDirectoryW(pszPath), "Line %d: RemoveDirectoryW(%ls) failed (%ld)\n",
       entry->line, pszPath, GetLastError());
    return TRUE;
}

static BOOL DoAction9(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_FILE);
    SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW | SHCNF_FLUSH, pszPath, NULL);
    return FALSE;
}

static BOOL DoAction10(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_FILE);
    SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW | SHCNF_FLUSH, pszPath, NULL);
    return FALSE;
}

static BOOL DoAction11(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_DIR);
    SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, pszPath, NULL);
    return FALSE;
}

static BOOL DoAction12(const TEST_ENTRY *entry)
{
    LPWSTR pszPath = DoGetDir(entry->iWriteDir);
    PathAppendW(pszPath, TEST_DIR);
    SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW | SHCNF_FLUSH, pszPath, NULL);
    return FALSE;
}

#define WRITEDIR_0 DIRTYPE_DESKTOP
static WCHAR s_szDesktop[MAX_PATH];
static WCHAR s_szTestFile0[MAX_PATH];
static WCHAR s_szTestFile0Kai[MAX_PATH];
static WCHAR s_szTestDir0[MAX_PATH];
static WCHAR s_szTestDir0Kai[MAX_PATH];

#define WRITEDIR_1 DIRTYPE_MYDOCUMENTS
static WCHAR s_szDocuments[MAX_PATH];
static WCHAR s_szTestFile1[MAX_PATH];
static WCHAR s_szTestFile1Kai[MAX_PATH];
static WCHAR s_szTestDir1[MAX_PATH];
static WCHAR s_szTestDir1Kai[MAX_PATH];

static void DoDeleteFilesAndDirs(void)
{
    DeleteFileW(TEMP_FILE);
    DeleteFileW(s_szTestFile0);
    DeleteFileW(s_szTestFile0Kai);
    DeleteFileW(s_szTestFile1);
    DeleteFileW(s_szTestFile1Kai);
    RemoveDirectoryW(s_szTestDir0);
    RemoveDirectoryW(s_szTestDir0Kai);
    RemoveDirectoryW(s_szTestDir1);
    RemoveDirectoryW(s_szTestDir1Kai);
}

static const TEST_ENTRY s_group_00[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction12 },
};

static const TEST_ENTRY s_group_01[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction12 },
};

static const TEST_ENTRY s_group_02[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction12 },
};

static const TEST_ENTRY s_group_03[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction12 },
};

static const TEST_ENTRY s_group_04[] =
{
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0, s_szTestFile0Kai, DoAction2 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0Kai, s_szTestFile0, DoAction3 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0, s_szTestDir0Kai, DoAction6 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0Kai, s_szTestDir0, DoAction7 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction12 },
};

static const TEST_ENTRY s_group_05[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "1000000", s_szTestFile1, s_szTestFile1Kai, DoAction2 },
    { __LINE__, WRITEDIR_1, "1000000", s_szTestFile1Kai, s_szTestFile1, DoAction3 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000010", s_szTestDir1, s_szTestDir1Kai, DoAction6 },
    { __LINE__, WRITEDIR_1, "0000010", s_szTestDir1Kai, s_szTestDir1, DoAction7 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction12 },
};

static const TEST_ENTRY s_group_06[] =
{
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0, s_szTestFile0Kai, DoAction2 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0Kai, s_szTestFile0, DoAction3 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0, s_szTestDir0Kai, DoAction6 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0Kai, s_szTestDir0, DoAction7 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction12 },
};

static const TEST_ENTRY s_group_07[] =
{
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0, s_szTestFile0Kai, DoAction2 },
    { __LINE__, WRITEDIR_0, "1000000", s_szTestFile0Kai, s_szTestFile0, DoAction3 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0, s_szTestDir0Kai, DoAction6 },
    { __LINE__, WRITEDIR_0, "0000010", s_szTestDir0Kai, s_szTestDir0, DoAction7 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0100000", s_szTestFile0, L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0010000", s_szTestFile0, L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0001000", s_szTestDir0, L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000100", s_szTestDir0, L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000000", L"", L"", DoAction12 },
};

static const TEST_ENTRY s_group_08[] =
{
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction1 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction2 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction3 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction4 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction5 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction6 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction7 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction8 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction9 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction10 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction11 },
    { __LINE__, WRITEDIR_0, "0000000", L"", L"", DoAction12 },

    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction1 },
    { __LINE__, WRITEDIR_1, "1000000", s_szTestFile1, s_szTestFile1Kai, DoAction2 },
    { __LINE__, WRITEDIR_1, "1000000", s_szTestFile1Kai, s_szTestFile1, DoAction3 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction4 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction5 },
    { __LINE__, WRITEDIR_1, "0000010", s_szTestDir1, s_szTestDir1Kai, DoAction6 },
    { __LINE__, WRITEDIR_1, "0000010", s_szTestDir1Kai, s_szTestDir1, DoAction7 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction8 },
    { __LINE__, WRITEDIR_1, "0100000", s_szTestFile1, L"", DoAction9 },
    { __LINE__, WRITEDIR_1, "0010000", s_szTestFile1, L"", DoAction10 },
    { __LINE__, WRITEDIR_1, "0001000", s_szTestDir1, L"", DoAction11 },
    { __LINE__, WRITEDIR_1, "0000100", s_szTestDir1, L"", DoAction12 },
};

static LPCSTR PatternFromFlags(DWORD flags)
{
    static CHAR s_buf[(TYPE_MAX + 1) + 1];
    for (INT i = 0; i < (TYPE_MAX + 1); ++i)
        s_buf[i] = (char)('0' + !!(flags & (1 << i)));

    s_buf[TYPE_MAX + 1] = 0;
    return s_buf;
}

static BOOL DoGetPaths(LPWSTR pszPath1, LPWSTR pszPath2)
{
    pszPath1[0] = pszPath2[0] = 0;

    WCHAR szText[MAX_PATH * 2];
    szText[0] = 0;
    FILE *fp = _wfopen(TEMP_FILE, L"rb");
    if (fp)
    {
        fread(szText, 1, sizeof(szText), fp);
        fclose(fp);
    }

    LPWSTR pch = wcschr(szText, L'|');
    if (pch == NULL)
        return FALSE;

    *pch = 0;
    StringCchCopyW(pszPath1, MAX_PATH, szText);
    StringCchCopyW(pszPath2, MAX_PATH, pch + 1);
    return TRUE;
}

static void DoTestEntry(INT iEntry, const TEST_ENTRY *entry, INT nSources)
{
#ifdef ENTRY_TICK
    DWORD dwOldTick = GetTickCount();
#endif

    BOOL bInterrupting = FALSE;
    if (entry->action)
        bInterrupting = entry->action(entry);

    DWORD flags;
    LPCSTR pattern;
    if ((nSources & SHCNRF_InterruptLevel) && bInterrupting)
    {
        // The event won't work at here. Manually waiting...
        UINT cTry = ((iEntry == 0) ? 100 : 60);
        for (UINT iTry = 0; iTry < cTry; ++iTry)
        {
            flags = SendMessageW(s_hwnd, WM_GET_NOTIFY_FLAGS, 0, 0);
            pattern = PatternFromFlags(flags);
            if (strcmp(pattern, "0000000") != 0)
                break;

            Sleep(1);
        }
    }
    else
    {
        if (WaitForSingleObject(s_hEvent, 100) == WAIT_OBJECT_0)
            Sleep(1);

        flags = SendMessageW(s_hwnd, WM_GET_NOTIFY_FLAGS, 0, 0);
        pattern = PatternFromFlags(flags);
    }

    SendMessageW(s_hwnd, WM_SET_PATHS, 0, 0);

    WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
    szPath1[0] = szPath2[0] = 0;
    BOOL bOK = DoGetPaths(szPath1, szPath2);

    static UINT s_cCalmDown = 0;

    if (pattern[TYPE_UPDATEDIR] == '1')
    {
        trace("Line %d: SHCNE_UPDATEDIR: Calm down (%u)...\n", entry->line, s_cCalmDown);

        if (++s_cCalmDown < 3)
            Sleep(3000);

        if (entry->pattern)
            ok(TRUE, "Line %d:\n", entry->line);
        if (entry->path1)
            ok(TRUE, "Line %d:\n", entry->line);
        if (entry->path2)
            ok(TRUE, "Line %d:\n", entry->line);
    }
    else
    {
        s_cCalmDown = 0;
        if (entry->pattern)
        {
            ok(strcmp(pattern, entry->pattern) == 0,
               "Line %d: pattern mismatch '%s', tick=0x%08lX\n",
               entry->line, pattern, GetTickCount());
        }
        if (entry->path1)
            ok(bOK && lstrcmpiW(entry->path1, szPath1) == 0,
               "Line %d: path1 mismatch '%S' (%d)\n", entry->line, szPath1, bOK);
        if (entry->path2)
            ok(bOK && lstrcmpiW(entry->path2, szPath2) == 0,
               "Line %d: path2 mismatch '%S' (%d)\n", entry->line, szPath2, bOK);
    }

    SendMessageW(s_hwnd, WM_CLEAR_FLAGS, 0, 0);
    ResetEvent(s_hEvent);

#ifdef ENTRY_TICK
    DWORD dwNewTick = GetTickCount();
    DWORD dwTick = dwNewTick - dwOldTick;
    trace("DoTestEntry: Line %d: tick=%lu.%lu sec\n", entry->line,
          (dwTick / 1000), (dwTick / 100 % 10));
#endif
}

static void DoQuitTest(BOOL bForce)
{
    PostMessageW(s_hwnd, WM_COMMAND, IDOK, 0);

    DoWaitForWindow(CLASSNAME, CLASSNAME, TRUE, bForce);
    s_hwnd = NULL;

    if (s_hEvent)
    {
        CloseHandle(s_hEvent);
        s_hEvent = NULL;
    }

    DoDeleteFilesAndDirs();
}

static void DoAbortThread(void)
{
    skip("Aborting the thread...\n");
    if (s_hThread)
    {
        TerminateThread(s_hThread, -1);
        s_hThread = NULL;
    }
}

static BOOL CALLBACK HandlerRoutine(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {
        case CTRL_C_EVENT:
        case CTRL_BREAK_EVENT:
            DoAbortThread();
            DoQuitTest(TRUE);
            return TRUE;
    }
    return FALSE;
}

static BOOL DoInitTest(void)
{
    // DIRTYPE_DESKTOP
    LPWSTR psz = DoGetDir(DIRTYPE_DESKTOP);
    StringCchCopyW(s_szDesktop, _countof(s_szDesktop), psz);

    PathAppendW(psz, TEST_FILE);
    StringCchCopyW(s_szTestFile0, _countof(s_szTestFile0), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_FILE_KAI);
    StringCchCopyW(s_szTestFile0Kai, _countof(s_szTestFile0Kai), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_DIR);
    StringCchCopyW(s_szTestDir0, _countof(s_szTestDir0), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_DIR_KAI);
    StringCchCopyW(s_szTestDir0Kai, _countof(s_szTestDir0Kai), psz);

    // DIRTYPE_MYDOCUMENTS
    psz = DoGetDir(DIRTYPE_MYDOCUMENTS);
    StringCchCopyW(s_szDocuments, _countof(s_szDocuments), psz);

    PathAppendW(psz, TEST_FILE);
    StringCchCopyW(s_szTestFile1, _countof(s_szTestFile1), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_FILE_KAI);
    StringCchCopyW(s_szTestFile1Kai, _countof(s_szTestFile1Kai), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_DIR);
    StringCchCopyW(s_szTestDir1, _countof(s_szTestDir1), psz);

    PathRemoveFileSpecW(psz);
    PathAppendW(psz, TEST_DIR_KAI);
    StringCchCopyW(s_szTestDir1Kai, _countof(s_szTestDir1Kai), psz);

    // prepare for files and dirs
    DoDeleteFilesAndDirs();
    DoCreateEmptyFile(TEMP_FILE);

    // Ctrl+C
    SetConsoleCtrlHandler(HandlerRoutine, TRUE);

    // close Explorer windows
    trace("Closing Explorer windows...\n");
    DoWaitForWindow(L"CabinetWClass", NULL, TRUE, TRUE);

    // close the CLASSNAME windows
    return DoWaitForWindow(CLASSNAME, CLASSNAME, TRUE, TRUE) == NULL;
}

static BOOL
GetSubProgramPath(void)
{
    GetModuleFileNameW(NULL, s_szSubProgram, _countof(s_szSubProgram));
    PathRemoveFileSpecW(s_szSubProgram);
    PathAppendW(s_szSubProgram, L"shell32_apitest_sub.exe");

    if (!PathFileExistsW(s_szSubProgram))
    {
        PathRemoveFileSpecW(s_szSubProgram);
        PathAppendW(s_szSubProgram, L"testdata\\shell32_apitest_sub.exe");

        if (!PathFileExistsW(s_szSubProgram))
            return FALSE;
    }

    return TRUE;
}

#define SRC_00 0
#define SRC_01 SHCNRF_ShellLevel
#define SRC_02 (SHCNRF_NewDelivery)
#define SRC_03 (SHCNRF_NewDelivery | SHCNRF_ShellLevel)
#define SRC_04 SHCNRF_InterruptLevel
#define SRC_05 (SHCNRF_InterruptLevel | SHCNRF_ShellLevel)
#define SRC_06 (SHCNRF_InterruptLevel | SHCNRF_NewDelivery)
#define SRC_07 (SHCNRF_InterruptLevel | SHCNRF_NewDelivery | SHCNRF_ShellLevel)
#define SRC_08 (SHCNRF_RecursiveInterrupt | SHCNRF_InterruptLevel)
#define SRC_09 (SHCNRF_RecursiveInterrupt | SHCNRF_InterruptLevel | SHCNRF_ShellLevel)
#define SRC_10 (SHCNRF_RecursiveInterrupt | SHCNRF_InterruptLevel | SHCNRF_NewDelivery)
#define SRC_11 (SHCNRF_RecursiveInterrupt | SHCNRF_InterruptLevel | SHCNRF_NewDelivery | SHCNRF_ShellLevel)

#define WATCHDIR_0 DIRTYPE_NULL
#define WATCHDIR_1 DIRTYPE_DESKTOP
#define WATCHDIR_2 DIRTYPE_MYCOMPUTER
#define WATCHDIR_3 DIRTYPE_MYDOCUMENTS

static void
DoTestGroup(INT line, UINT cEntries, const TEST_ENTRY *pEntries, BOOL fRecursive,
            INT nSources, DIRTYPE iWatchDir)
{
#ifdef NO_INTERRUPT_LEVEL
    if (nSources & SHCNRF_InterruptLevel)
        return;
#endif
#ifdef NO_SHELL_LEVEL
    if (nSources & SHCNRF_ShellLevel)
        return;
#endif
#ifdef NEW_DELIVERY_ONLY
    if (!(nSources & SHCNRF_NewDelivery))
        return;
#endif
#ifdef GROUP_TICK
    DWORD dwOldTick = GetTickCount();
#endif
#ifdef RANDOM_QUARTER
    if ((rand() & 3) == 0)
        return;
#elif defined(RANDOM_HALF)
    if (rand() & 1)
        return;
#endif

    trace("DoTestGroup: Line %d: fRecursive:%u, iWatchDir:%u, nSources:0x%X\n",
          line, fRecursive, iWatchDir, nSources);

    if (s_hEvent)
    {
        CloseHandle(s_hEvent);
        s_hEvent = NULL;
    }
    s_hEvent = CreateEventW(NULL, TRUE, FALSE, EVENT_NAME);

    WCHAR szParams[64];
    StringCchPrintfW(szParams, _countof(szParams), L"%u,%u,%u", fRecursive, iWatchDir, nSources);

    HINSTANCE hinst = ShellExecuteW(NULL, NULL, s_szSubProgram, szParams, NULL, SW_SHOWNORMAL);
    if ((INT_PTR)hinst <= 32)
    {
        skip("Unable to run shell32_apitest_sub.exe.\n");
        return;
    }

    s_hwnd = DoWaitForWindow(CLASSNAME, CLASSNAME, FALSE, FALSE);
    if (!s_hwnd)
    {
        skip("Unable to find window.\n");
        return;
    }

    for (UINT i = 0; i < cEntries; ++i)
    {
        if (!IsWindow(s_hwnd))
        {
            DoAbortThread();
            DoQuitTest(TRUE);
            break;
        }

        DoTestEntry(i, &pEntries[i], nSources);
    }

    DoQuitTest(FALSE);

#ifdef GROUP_TICK
    DWORD dwNewTick = GetTickCount();
    DWORD dwTick = dwNewTick - dwOldTick;
    trace("DoTestGroup: Line %d: %lu.%lu sec\n", line, (dwTick / 1000), (dwTick / 100 % 10));
#endif
}

static unsigned __stdcall TestThreadProc(void *)
{
    srand(time(NULL));
#ifdef RANDOM_QUARTER
    skip("RANDOM_QUARTER\n");
#elif defined(RANDOM_HALF)
    skip("RANDOM_HALF\n");
#endif

    // fRecursive == FALSE.
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_00, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, FALSE, SRC_01, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_02, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, FALSE, SRC_03, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, FALSE, SRC_04, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, FALSE, SRC_05, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, FALSE, SRC_06, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, FALSE, SRC_07, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, FALSE, SRC_08, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, FALSE, SRC_09, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, FALSE, SRC_10, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, FALSE, SRC_11, WATCHDIR_0);

    BOOL bTarget = IsWindowsXPOrGreater() && !IsWindowsVistaOrGreater();

#define SWITCH(x, y) (bTarget ? (x) : (y))
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_00, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_03), s_group_03, FALSE, SRC_01, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_02, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_03), s_group_03, FALSE, SRC_03, WATCHDIR_1);
    DoTestGroup(__LINE__, SWITCH(_countof(s_group_00), _countof(s_group_04)), SWITCH(s_group_00, s_group_04), FALSE, SRC_04, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_07), s_group_07, FALSE, SRC_05, WATCHDIR_1);
    DoTestGroup(__LINE__, SWITCH(_countof(s_group_00), _countof(s_group_04)), SWITCH(s_group_00, s_group_04), FALSE, SRC_06, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_07), s_group_07, FALSE, SRC_07, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, FALSE, SRC_08, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_07), s_group_07, FALSE, SRC_09, WATCHDIR_1);
    DoTestGroup(__LINE__, SWITCH(_countof(s_group_00), _countof(s_group_04)), SWITCH(s_group_00, s_group_04), FALSE, SRC_06, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_07), s_group_07, FALSE, SRC_11, WATCHDIR_1);
#undef SWITCH

#ifndef NO_TRIVIAL
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_00, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_01, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_02, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_03, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_04, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_05, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_06, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_07, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_08, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_09, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_10, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_11, WATCHDIR_2);
#endif

    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_00, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_02), s_group_02, FALSE, SRC_01, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, FALSE, SRC_02, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_02), s_group_02, FALSE, SRC_03, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, FALSE, SRC_04, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, FALSE, SRC_05, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, FALSE, SRC_06, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, FALSE, SRC_07, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, FALSE, SRC_08, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, FALSE, SRC_09, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, FALSE, SRC_10, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, FALSE, SRC_11, WATCHDIR_3);

    // fRecursive == TRUE.
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_00, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_01, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_02, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_03, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_04, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_05, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_06, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_07, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_08, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_09, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_10, WATCHDIR_0);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_11, WATCHDIR_0);

    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_00, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_01, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_02, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_03, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_04, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_05, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_06, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_07, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_08, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_09, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_10, WATCHDIR_1);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_11, WATCHDIR_1);

    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_00, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_01, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_02, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_01), s_group_01, TRUE, SRC_03, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_04, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_05, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_06, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_07, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_08, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_09, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_04), s_group_04, TRUE, SRC_10, WATCHDIR_2);
    DoTestGroup(__LINE__, _countof(s_group_06), s_group_06, TRUE, SRC_11, WATCHDIR_2);

    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_00, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_02), s_group_02, TRUE, SRC_01, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_00), s_group_00, TRUE, SRC_02, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_02), s_group_02, TRUE, SRC_03, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, TRUE, SRC_04, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, TRUE, SRC_05, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, TRUE, SRC_06, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, TRUE, SRC_07, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, TRUE, SRC_08, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, TRUE, SRC_09, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_05), s_group_05, TRUE, SRC_10, WATCHDIR_3);
    DoTestGroup(__LINE__, _countof(s_group_08), s_group_08, TRUE, SRC_11, WATCHDIR_3);

    return 0;
}

START_TEST(SHChangeNotify)
{
#ifdef TOTAL_TICK
    DWORD dwOldTick = GetTickCount();
#endif
#ifdef DISABLE_THIS_TESTCASE
    skip("This testcase is disabled by DISABLE_THIS_TESTCASE macro.\n");
    return;
#endif

    trace("Please don't operate your PC while testing...\n");

    if (!GetSubProgramPath())
    {
        skip("shell32_apitest_sub.exe not found\n");
        return;
    }

    if (!DoInitTest())
    {
        skip("Unable to initialize.\n");
        DoQuitTest(TRUE);
        return;
    }

    s_hThread = (HANDLE)_beginthreadex(NULL, 0, TestThreadProc, NULL, 0, NULL);
    WaitForSingleObject(s_hThread, INFINITE);
    CloseHandle(s_hThread);

#ifdef TOTAL_TICK
    DWORD dwNewTick = GetTickCount();
    DWORD dwTick = dwNewTick - dwOldTick;
    trace("SHChangeNotify: Total %lu.%lu sec\n", (dwTick / 1000), (dwTick / 100 % 10));
#endif

    DoWaitForWindow(CLASSNAME, CLASSNAME, TRUE, TRUE);
    Sleep(100);
}