/*
 * Copyright 2008 James Hawkins
 *
 * 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 <stdio.h>

#include <windows.h>
#include <shlwapi.h>
#include <mscoree.h>
#include <fusion.h>
#include <corerror.h>

#include "wine/test.h"
#include "wine/list.h"

static HRESULT (WINAPI *pCreateAssemblyEnum)(IAssemblyEnum **pEnum,
                                             IUnknown *pUnkReserved,
                                             IAssemblyName *pName,
                                             DWORD dwFlags, LPVOID pvReserved);
static HRESULT (WINAPI *pCreateAssemblyNameObject)(LPASSEMBLYNAME *ppAssemblyNameObj,
                                                   LPCWSTR szAssemblyName, DWORD dwFlags,
                                                   LPVOID pvReserved);
static HRESULT (WINAPI *pGetCachePath)(ASM_CACHE_FLAGS dwCacheFlags,
                                       LPWSTR pwzCachePath, PDWORD pcchPath);
static HRESULT (WINAPI *pLoadLibraryShim)(LPCWSTR szDllName, LPCWSTR szVersion,
                                          LPVOID pvReserved, HMODULE *phModDll);

static BOOL init_functionpointers(void)
{
    HRESULT hr;
    HMODULE hfusion;
    HMODULE hmscoree;

    static const WCHAR szFusion[] = {'f','u','s','i','o','n','.','d','l','l',0};

    hmscoree = LoadLibraryA("mscoree.dll");
    if (!hmscoree)
    {
        win_skip("mscoree.dll not available\n");
        return FALSE;
    }

    pLoadLibraryShim = (void *)GetProcAddress(hmscoree, "LoadLibraryShim");
    if (!pLoadLibraryShim)
    {
        win_skip("LoadLibraryShim not available\n");
        FreeLibrary(hmscoree);
        return FALSE;
    }

    hr = pLoadLibraryShim(szFusion, NULL, NULL, &hfusion);
    if (FAILED(hr))
    {
        win_skip("fusion.dll not available\n");
        FreeLibrary(hmscoree);
        return FALSE;
    }

    pCreateAssemblyEnum = (void *)GetProcAddress(hfusion, "CreateAssemblyEnum");
    pCreateAssemblyNameObject = (void *)GetProcAddress(hfusion, "CreateAssemblyNameObject");
    pGetCachePath = (void *)GetProcAddress(hfusion, "GetCachePath");

    if (!pCreateAssemblyEnum ||
        !pCreateAssemblyNameObject || !pGetCachePath)
    {
        win_skip("fusion.dll not implemented\n");
        return FALSE;
    }

    FreeLibrary(hmscoree);
    return TRUE;
}

static inline void to_widechar(LPWSTR dest, LPCSTR src)
{
    MultiByteToWideChar(CP_ACP, 0, src, -1, dest, MAX_PATH);
}

static inline void to_multibyte(LPSTR dest, LPWSTR src)
{
    WideCharToMultiByte(CP_ACP, 0, src, -1, dest, MAX_PATH, NULL, NULL);
}

static BOOL create_full_path(LPCSTR path)
{
    LPSTR new_path;
    BOOL ret = TRUE;
    int len;

    new_path = HeapAlloc(GetProcessHeap(), 0, lstrlenA(path) + 1);
    if (!new_path)
        return FALSE;

    lstrcpyA(new_path, path);

    while ((len = lstrlenA(new_path)) && new_path[len - 1] == '\\')
        new_path[len - 1] = 0;

    while (!CreateDirectoryA(new_path, NULL))
    {
        LPSTR slash;
        DWORD last_error = GetLastError();

        if(last_error == ERROR_ALREADY_EXISTS)
            break;

        if(last_error != ERROR_PATH_NOT_FOUND)
        {
            ret = FALSE;
            break;
        }

        if(!(slash = strrchr(new_path, '\\')))
        {
            ret = FALSE;
            break;
        }

        len = slash - new_path;
        new_path[len] = 0;
        if(!create_full_path(new_path))
        {
            ret = FALSE;
            break;
        }

        new_path[len] = '\\';
    }

    HeapFree(GetProcessHeap(), 0, new_path);
    return ret;
}

static BOOL create_file_data(LPCSTR name, LPCSTR data, DWORD size)
{
    HANDLE file;
    DWORD written;

    file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    if (file == INVALID_HANDLE_VALUE)
        return FALSE;

    WriteFile(file, data, strlen(data), &written, NULL);

    if (size)
    {
        SetFilePointer(file, size, NULL, FILE_BEGIN);
        SetEndOfFile(file);
    }

    CloseHandle(file);
    return TRUE;
}

static void test_CreateAssemblyEnum(void)
{
    HRESULT hr;
    WCHAR namestr[MAX_PATH];
    IAssemblyEnum *asmenum;
    IAssemblyName *asmname;

    to_widechar(namestr, "wine");
    asmname = NULL;
    hr = pCreateAssemblyNameObject(&asmname, namestr, CANOF_PARSE_DISPLAY_NAME, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmname != NULL, "Expected non-NULL asmname\n");

    /* pEnum is NULL */
    if (0)
    {
        /* Crashes on .NET 1.x */
        hr = pCreateAssemblyEnum(NULL, NULL, asmname, ASM_CACHE_GAC, NULL);
        ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", hr);
    }

    /* pName is NULL */
    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, NULL, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    IAssemblyEnum_Release(asmenum);

    /* dwFlags is ASM_CACHE_ROOT */
    asmenum = (IAssemblyEnum *)0xdeadbeef;
    hr = pCreateAssemblyEnum(&asmenum, NULL, NULL, ASM_CACHE_ROOT, NULL);
    ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", hr);
    ok(asmenum == (IAssemblyEnum *)0xdeadbeef,
       "Expected asmenum to be unchanged, got %p\n", asmenum);

    /* invalid dwFlags */
    asmenum = (IAssemblyEnum *)0xdeadbeef;
    hr = pCreateAssemblyEnum(&asmenum, NULL, NULL, 0, NULL);
    ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", hr);
    ok(asmenum == (IAssemblyEnum *)0xdeadbeef,
       "Expected asmenum to be unchanged, got %p\n", asmenum);

    IAssemblyName_Release(asmname);
}

typedef struct _tagASMNAME
{
    struct list entry;
    LPSTR data;
} ASMNAME;

static BOOL enum_gac_assemblies(struct list *assemblies, int depth, LPSTR path)
{
    WIN32_FIND_DATAA ffd;
    CHAR buf[MAX_PATH];
    CHAR disp[MAX_PATH];
    ASMNAME *name;
    HANDLE hfind;
    LPSTR ptr;

    static CHAR parent[MAX_PATH];

    sprintf(buf, "%s\\*", path);
    hfind = FindFirstFileA(buf, &ffd);
    if (hfind == INVALID_HANDLE_VALUE)
        return FALSE;

    do
    {
        if (!lstrcmpA(ffd.cFileName, ".") || !lstrcmpA(ffd.cFileName, ".."))
            continue;

        if (depth == 0)
        {
            lstrcpyA(parent, ffd.cFileName);
        }
        else if (depth == 1)
        {
            char culture[MAX_PATH];
            char dll[MAX_PATH], exe[MAX_PATH];

            /* Directories with no dll or exe will not be enumerated */
            sprintf(dll, "%s\\%s\\%s.dll", path, ffd.cFileName, parent);
            sprintf(exe, "%s\\%s\\%s.exe", path, ffd.cFileName, parent);
            if (GetFileAttributesA(dll) == INVALID_FILE_ATTRIBUTES &&
                GetFileAttributesA(exe) == INVALID_FILE_ATTRIBUTES)
                continue;

            ptr = strstr(ffd.cFileName, "_");
            *ptr = '\0';
            ptr++;

            if (*ptr != '_')
            {
                lstrcpyA(culture, ptr);
                *strstr(culture, "_") = '\0';
            }
            else
                lstrcpyA(culture, "neutral");

            ptr = strchr(ptr, '_');
            ptr++;
            sprintf(buf, ", Version=%s, Culture=%s, PublicKeyToken=%s",
                    ffd.cFileName, culture, ptr);
            lstrcpyA(disp, parent);
            lstrcatA(disp, buf);

            name = HeapAlloc(GetProcessHeap(), 0, sizeof(ASMNAME));
            name->data = HeapAlloc(GetProcessHeap(), 0, lstrlenA(disp) + 1);
            lstrcpyA(name->data, disp);
            list_add_tail(assemblies, &name->entry);

            continue;
        }

        sprintf(buf, "%s\\%s", path, ffd.cFileName);
        enum_gac_assemblies(assemblies, depth + 1, buf);
    } while (FindNextFileA(hfind, &ffd) != 0);

    FindClose(hfind);
    return TRUE;
}

static void test_enumerate(void)
{
    struct list assemblies = LIST_INIT(assemblies);
    struct list *item, *cursor;
    IAssemblyEnum *asmenum;
    IAssemblyName *next;
    WCHAR buf[MAX_PATH];
    CHAR path[MAX_PATH];
    CHAR disp[MAX_PATH];
    HRESULT hr;
    BOOL found;
    DWORD size;

    size = MAX_PATH;
    hr = pGetCachePath(ASM_CACHE_GAC, buf, &size);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);

    to_multibyte(path, buf);
    lstrcatA(path, "_32");
    enum_gac_assemblies(&assemblies, 0, path);

    to_multibyte(path, buf);
    lstrcatA(path, "_64");
    enum_gac_assemblies(&assemblies, 0, path);

    to_multibyte(path, buf);
    lstrcatA(path, "_MSIL");
    enum_gac_assemblies(&assemblies, 0, path);

    to_multibyte(path, buf);
    enum_gac_assemblies(&assemblies, 0, path);

    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, NULL, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    while (IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0) == S_OK)
    {
        size = MAX_PATH;
        IAssemblyName_GetDisplayName(next, buf, &size, 0);
        to_multibyte(disp, buf);

        found = FALSE;
        LIST_FOR_EACH_SAFE(item, cursor, &assemblies)
        {
            ASMNAME *asmname = LIST_ENTRY(item, ASMNAME, entry);

            if (!lstrcmpA(asmname->data, disp))
            {
                found = TRUE;

                list_remove(&asmname->entry);
                HeapFree(GetProcessHeap(), 0, asmname->data);
                HeapFree(GetProcessHeap(), 0, asmname);
                break;
            }
        }

        ok(found, "Extra assembly enumerated: %s\n", disp);
        IAssemblyName_Release(next);
    }

    /* enumeration is exhausted */
    next = (IAssemblyName *)0xdeadbeef;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_FALSE, "Expected S_FALSE, got %08x\n", hr);
    ok(next == (IAssemblyName *)0xdeadbeef,
       "Expected next to be unchanged, got %p\n", next);

    LIST_FOR_EACH_SAFE(item, cursor, &assemblies)
    {
        ASMNAME *asmname = LIST_ENTRY(item, ASMNAME, entry);

        ok(FALSE, "Assembly not enumerated: %s\n", asmname->data);

        list_remove(&asmname->entry);
        HeapFree(GetProcessHeap(), 0, asmname->data);
        HeapFree(GetProcessHeap(), 0, asmname);
    }

    IAssemblyEnum_Release(asmenum);
}

static void test_enumerate_name(void)
{
    IAssemblyEnum *asmenum;
    IAssemblyName *asmname, *next;
    WCHAR buf[MAX_PATH];
    CHAR gac[MAX_PATH];
    CHAR path[MAX_PATH];
    CHAR disp[MAX_PATH];
    WCHAR namestr[MAX_PATH];
    CHAR exp[6][MAX_PATH];
    HRESULT hr;
    DWORD size;

    lstrcpyA(exp[0], "wine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=16a3fcd171e93a8d");
    lstrcpyA(exp[1], "wine, Version=1.0.1.2, Culture=neutral, PublicKeyToken=123456789abcdef0");
    lstrcpyA(exp[2], "wine, Version=1.0.1.2, Culture=neutral, PublicKeyToken=16a3fcd171e93a8d");
    lstrcpyA(exp[3], "Wine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=16a3fcd171e93a8d");
    lstrcpyA(exp[4], "Wine, Version=1.0.1.2, Culture=neutral, PublicKeyToken=123456789abcdef0");
    lstrcpyA(exp[5], "Wine, Version=1.0.1.2, Culture=neutral, PublicKeyToken=16a3fcd171e93a8d");

    size = MAX_PATH;
    hr = pGetCachePath(ASM_CACHE_GAC, buf, &size);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);

    to_multibyte(gac, buf);
    create_full_path(gac);

    sprintf(path, "%s\\Wine", gac);
    CreateDirectoryA(path, NULL);

    sprintf(path, "%s\\Wine\\1.0.0.0__16a3fcd171e93a8d", gac);
    CreateDirectoryA(path, NULL);

    lstrcatA(path, "\\Wine.dll");
    if (!create_file_data(path, path, 100))
    {
        win_skip("Failed to open file %s, skipping name enumeration tests\n", path);
        goto done;
    }

    sprintf(path, "%s\\Wine\\1.0.1.2__16a3fcd171e93a8d", gac);
    CreateDirectoryA(path, NULL);

    lstrcatA(path, "\\Wine.dll");
    if (!create_file_data(path, path, 100))
    {
        win_skip("Failed to open file %s, skipping name enumeration tests\n", path);
        goto done;
    }

    sprintf(path, "%s\\Wine\\1.0.1.2__123456789abcdef0", gac);
    CreateDirectoryA(path, NULL);

    lstrcatA(path, "\\Wine.dll");
    if (!create_file_data(path, path, 100))
    {
        win_skip("Failed to open file %s, skipping name enumeration tests\n", path);
        goto done;
    }

    /* test case sensitivity */
    to_widechar(namestr, "wine");
    asmname = NULL;
    hr = pCreateAssemblyNameObject(&asmname, namestr, CANOF_PARSE_DISPLAY_NAME, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmname != NULL, "Expected non-NULL asmname\n");

    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, asmname, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[0]),
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[0], exp[1], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[1]) ||
       !lstrcmpA(disp, exp[2]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[1], exp[2], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[2]) ||
       !lstrcmpA(disp, exp[1]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[2], exp[1], disp);

    IAssemblyName_Release(next);

    next = (IAssemblyName *)0xdeadbeef;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_FALSE, "Expected S_FALSE, got %08x\n", hr);
    ok(next == (IAssemblyName *)0xdeadbeef,
       "Expected next to be unchanged, got %p\n", next);

    IAssemblyEnum_Release(asmenum);
    IAssemblyName_Release(asmname);

    /* only Version */
    to_widechar(namestr, "Wine, Version=1.0.1.2");
    asmname = NULL;
    hr = pCreateAssemblyNameObject(&asmname, namestr, CANOF_PARSE_DISPLAY_NAME, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmname != NULL, "Expected non-NULL asmname\n");

    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, asmname, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[4]) ||
       !lstrcmpA(disp, exp[5]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[4], exp[5], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[5]) ||
       !lstrcmpA(disp, exp[4]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[5], exp[4], disp);

    IAssemblyName_Release(next);

    next = (IAssemblyName *)0xdeadbeef;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_FALSE, "Expected S_FALSE, got %08x\n", hr);
    ok(next == (IAssemblyName *)0xdeadbeef,
       "Expected next to be unchanged, got %p\n", next);

    IAssemblyEnum_Release(asmenum);
    IAssemblyName_Release(asmname);

    /* only PublicKeyToken */
    to_widechar(namestr, "Wine, PublicKeyToken=16a3fcd171e93a8d");
    asmname = NULL;
    hr = pCreateAssemblyNameObject(&asmname, namestr, CANOF_PARSE_DISPLAY_NAME, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmname != NULL, "Expected non-NULL asmname\n");

    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, asmname, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[3]), "Expected \"%s\", got \"%s\"\n", exp[3], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[5]), "Expected \"%s\", got \"%s\"\n", exp[5], disp);

    IAssemblyName_Release(next);

    next = (IAssemblyName *)0xdeadbeef;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_FALSE, "Expected S_FALSE, got %08x\n", hr);
    ok(next == (IAssemblyName *)0xdeadbeef,
       "Expected next to be unchanged, got %p\n", next);

    IAssemblyEnum_Release(asmenum);
    IAssemblyName_Release(asmname);

    /* only Culture */
    to_widechar(namestr, "wine, Culture=neutral");
    asmname = NULL;
    hr = pCreateAssemblyNameObject(&asmname, namestr, CANOF_PARSE_DISPLAY_NAME, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmname != NULL, "Expected non-NULL asmname\n");

    asmenum = NULL;
    hr = pCreateAssemblyEnum(&asmenum, NULL, asmname, ASM_CACHE_GAC, NULL);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(asmenum != NULL, "Expected non-NULL asmenum\n");

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[0]), "Expected \"%s\", got \"%s\"\n", exp[0], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[1]) ||
       !lstrcmpA(disp, exp[2]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[1], exp[2], disp);

    IAssemblyName_Release(next);

    next = NULL;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(next != NULL, "Expected non-NULL next\n");

    size = MAX_PATH;
    hr = IAssemblyName_GetDisplayName(next, buf, &size, 0);
    to_multibyte(disp, buf);
    ok(hr == S_OK, "Expected S_OK, got %08x\n", hr);
    ok(!lstrcmpA(disp, exp[2]) ||
       !lstrcmpA(disp, exp[1]), /* Win98 */
       "Expected \"%s\" or \"%s\", got \"%s\"\n", exp[2], exp[1], disp);

    IAssemblyName_Release(next);

    next = (IAssemblyName *)0xdeadbeef;
    hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0);
    ok(hr == S_FALSE, "Expected S_FALSE, got %08x\n", hr);
    ok(next == (IAssemblyName *)0xdeadbeef,
       "Expected next to be unchanged, got %p\n", next);

    IAssemblyEnum_Release(asmenum);
    IAssemblyName_Release(asmname);

done:
    sprintf(path, "%s\\Wine\\1.0.0.0__16a3fcd171e93a8d\\Wine.dll", gac);
    DeleteFileA(path);
    sprintf(path, "%s\\Wine\\1.0.1.2__16a3fcd171e93a8d\\Wine.dll", gac);
    DeleteFileA(path);
    sprintf(path, "%s\\Wine\\1.0.1.2__123456789abcdef0\\Wine.dll", gac);
    DeleteFileA(path);
    sprintf(path, "%s\\Wine\\1.0.0.0__16a3fcd171e93a8d", gac);
    RemoveDirectoryA(path);
    sprintf(path, "%s\\Wine\\1.0.1.2__16a3fcd171e93a8d", gac);
    RemoveDirectoryA(path);
    sprintf(path, "%s\\Wine\\1.0.1.2__123456789abcdef0", gac);
    RemoveDirectoryA(path);
    sprintf(path, "%s\\Wine", gac);
    RemoveDirectoryA(path);
}

START_TEST(asmenum)
{
    if (!init_functionpointers())
        return;

    test_CreateAssemblyEnum();
    test_enumerate();
    test_enumerate_name();
}