/*
 *
 * Copyright 2012 Alistair Leslie-Hughes
 * Copyright 2014 Dmitry Timoshkov
 *
 * 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 <limits.h>

#include "windows.h"
#include "ole2.h"
#include "olectl.h"
#include "oleauto.h"
#include "dispex.h"
#include "shlwapi.h"

#include "wine/test.h"

#include "initguid.h"
#include "scrrun.h"

static IFileSystem3 *fs3;

static HRESULT (WINAPI *pDoOpenPipeStream)(HANDLE pipe, DWORD mode, ITextStream **stream);

/* w2k and 2k3 error code. */
#define E_VAR_NOT_SET 0x800a005b

static inline ULONG get_refcount(IUnknown *iface)
{
    IUnknown_AddRef(iface);
    return IUnknown_Release(iface);
}

static const char utf16bom[] = {0xff,0xfe,0};
static const WCHAR testfileW[] = L"test.txt";

#define GET_REFCOUNT(iface) \
    get_refcount((IUnknown*)iface)

static inline void get_temp_path(const WCHAR *prefix, WCHAR *path)
{
    WCHAR buffW[MAX_PATH];

    GetTempPathW(MAX_PATH, buffW);
    GetTempFileNameW(buffW, prefix, 0, path);
    DeleteFileW(path);
}

#define test_file_contents(a, b, c) _test_file_contents(a, b, c, __LINE__)
static void _test_file_contents(LPCWSTR pathW, DWORD expected_size, const void * expected_contents, int line)
{
    HANDLE file;
    BOOL ret;
    DWORD actual_size;

    file = CreateFileW(pathW, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    ok_(__FILE__,line) (file != INVALID_HANDLE_VALUE, "got %p\n", file);

    actual_size = GetFileSize(file,NULL);
    ok_(__FILE__,line) (actual_size == expected_size, "expected file %s size %ld, got %ld\n", wine_dbgstr_w(pathW), expected_size, actual_size);

    if((expected_contents != NULL) && (actual_size > 0))
    {
	DWORD r;
        void *actual_contents = malloc(actual_size);
        ret = ReadFile(file, actual_contents, actual_size, &r, NULL);
        ok_(__FILE__,line) (ret && r == actual_size, "Read %ld, got %d, error %ld.\n", r, ret, GetLastError());
        ok_(__FILE__,line) (!memcmp(expected_contents, actual_contents, expected_size), "expected file %s contents %s, got %s\n", wine_dbgstr_w(pathW), wine_dbgstr_an(expected_contents,expected_size), wine_dbgstr_an(actual_contents,r));
        free(actual_contents);
    }

    CloseHandle(file);
}

static IDrive *get_fixed_drive(void)
{
    IDriveCollection *drives;
    IEnumVARIANT *iter;
    IDrive *drive;
    HRESULT hr;

    hr = IFileSystem3_get_Drives(fs3, &drives);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IDriveCollection_get__NewEnum(drives, (IUnknown**)&iter);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    IDriveCollection_Release(drives);

    while (1) {
        DriveTypeConst type;
        VARIANT var;

        hr = IEnumVARIANT_Next(iter, 1, &var, NULL);
        if (hr == S_FALSE) {
            drive = NULL;
            break;
        }
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        hr = IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IDrive, (void**)&drive);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        VariantClear(&var);

        hr = IDrive_get_DriveType(drive, &type);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (type == Fixed)
            break;

        IDrive_Release(drive);
    }

    IEnumVARIANT_Release(iter);
    return drive;
}

#define test_provideclassinfo(a, b) _test_provideclassinfo((IDispatch*)a, b, __LINE__)
static void _test_provideclassinfo(IDispatch *disp, const GUID *guid, int line)
{
    IProvideClassInfo *classinfo;
    TYPEATTR *attr;
    ITypeInfo *ti;
    IUnknown *unk;
    HRESULT hr;

    hr = IDispatch_QueryInterface(disp, &IID_IProvideClassInfo, (void **)&classinfo);
    ok_(__FILE__,line) (hr == S_OK, "Failed to get IProvideClassInfo, %#lx.\n", hr);

    hr = IProvideClassInfo_GetClassInfo(classinfo, &ti);
    ok_(__FILE__,line) (hr == S_OK, "GetClassInfo() failed, %#lx.\n", hr);

    hr = ITypeInfo_GetTypeAttr(ti, &attr);
    ok_(__FILE__,line) (hr == S_OK, "GetTypeAttr() failed, %#lx.\n", hr);

    ok_(__FILE__,line) (IsEqualGUID(&attr->guid, guid), "Unexpected typeinfo %s, expected %s\n", wine_dbgstr_guid(&attr->guid),
        wine_dbgstr_guid(guid));

    hr = IProvideClassInfo_QueryInterface(classinfo, &IID_IUnknown, (void **)&unk);
    ok(hr == S_OK, "Failed to QI for IUnknown.\n");
    ok(unk == (IUnknown *)disp, "Got unk %p, original %p.\n", unk, disp);
    IUnknown_Release(unk);

    IProvideClassInfo_Release(classinfo);
    ITypeInfo_ReleaseTypeAttr(ti, attr);
    ITypeInfo_Release(ti);
}

static void test_interfaces(void)
{
    HRESULT hr;
    IDispatch *disp;
    IDispatchEx *dispex;
    IObjectWithSite *site;
    VARIANT_BOOL b;
    BSTR path;
    WCHAR windows_path[MAX_PATH];
    WCHAR file_path[MAX_PATH];

    IFileSystem3_QueryInterface(fs3, &IID_IDispatch, (void**)&disp);

    GetSystemDirectoryW(windows_path, MAX_PATH);
    lstrcpyW(file_path, windows_path);
    lstrcatW(file_path, L"\\kernel32.dll");

    test_provideclassinfo(disp, &CLSID_FileSystemObject);

    hr = IDispatch_QueryInterface(disp, &IID_IObjectWithSite, (void**)&site);
    ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr);

    hr = IDispatch_QueryInterface(disp, &IID_IDispatchEx, (void**)&dispex);
    ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr);

    b = VARIANT_TRUE;
    hr = IFileSystem3_FileExists(fs3, NULL, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "got %x\n", b);

    hr = IFileSystem3_FileExists(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    path = SysAllocString(L"path");
    b = VARIANT_TRUE;
    hr = IFileSystem3_FileExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "got %x\n", b);
    SysFreeString(path);

    path = SysAllocString(file_path);
    b = VARIANT_FALSE;
    hr = IFileSystem3_FileExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE, "got %x\n", b);
    SysFreeString(path);

    path = SysAllocString(windows_path);
    b = VARIANT_TRUE;
    hr = IFileSystem3_FileExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "got %x\n", b);
    SysFreeString(path);

    /* Folder Exists */
    hr = IFileSystem3_FolderExists(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    path = SysAllocString(windows_path);
    hr = IFileSystem3_FolderExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE, "Folder doesn't exists\n");
    SysFreeString(path);

    path = SysAllocString(L"c:\\Nonexistent");
    hr = IFileSystem3_FolderExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "Folder exists\n");
    SysFreeString(path);

    path = SysAllocString(file_path);
    hr = IFileSystem3_FolderExists(fs3, path, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "Folder exists\n");
    SysFreeString(path);

    IDispatch_Release(disp);
}

static void test_createfolder(void)
{
    WCHAR buffW[MAX_PATH];
    HRESULT hr;
    BSTR path;
    IFolder *folder;
    BOOL ret;

    get_temp_path(NULL, buffW);
    ret = CreateDirectoryW(buffW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    /* create existing directory */
    path = SysAllocString(buffW);
    folder = (void*)0xdeabeef;
    hr = IFileSystem3_CreateFolder(fs3, path, &folder);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);
    ok(folder == NULL, "got %p\n", folder);
    SysFreeString(path);
    RemoveDirectoryW(buffW);
}

static void test_textstream(void)
{
    WCHAR ExpectedW[10];
    ITextStream *stream;
    VARIANT_BOOL b;
    DWORD written;
    HANDLE file;
    HRESULT hr;
    BSTR name, data;
    BOOL ret;

    file = CreateFileW(testfileW, GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    CloseHandle(file);

    name = SysAllocString(testfileW);
    b = VARIANT_FALSE;
    hr = IFileSystem3_FileExists(fs3, name, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE, "got %x\n", b);

    /* different mode combinations */
    hr = IFileSystem3_OpenTextFile(fs3, name, ForWriting | ForAppending, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForReading | ForAppending, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForWriting | ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForAppending, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = ITextStream_Read(stream, 1, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForWriting, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = ITextStream_Read(stream, 1, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    /* try to write when open for reading */
    hr = ITextStream_WriteLine(stream, name);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Write(stream, name);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_get_AtEndOfStream(stream, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    b = 10;
    hr = ITextStream_get_AtEndOfStream(stream, &b);
    ok(hr == S_OK || broken(hr == S_FALSE), "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE, "got 0x%x\n", b);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForWriting, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    b = 10;
    hr = ITextStream_get_AtEndOfStream(stream, &b);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE || broken(b == 10), "got 0x%x\n", b);

    b = 10;
    hr = ITextStream_get_AtEndOfLine(stream, &b);
todo_wine {
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE || broken(b == 10), "got 0x%x\n", b);
}
    hr = ITextStream_Read(stream, 1, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_ReadLine(stream, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_ReadAll(stream, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForAppending, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    b = 10;
    hr = ITextStream_get_AtEndOfStream(stream, &b);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_TRUE || broken(b == 10), "got 0x%x\n", b);

    b = 10;
    hr = ITextStream_get_AtEndOfLine(stream, &b);
todo_wine {
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE || broken(b == 10), "got 0x%x\n", b);
}
    hr = ITextStream_Read(stream, 1, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_ReadLine(stream, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_ReadAll(stream, &data);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    ITextStream_Release(stream);

    /* now with non-empty file */
    file = CreateFileW(testfileW, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    ret = WriteFile(file, testfileW, sizeof(testfileW), &written, NULL);
    ok(ret && written == sizeof(testfileW), "got %d\n", ret);
    CloseHandle(file);

    /* opening a non-empty file (from above) for writing should truncate it */
    hr = IFileSystem3_OpenTextFile(fs3, name, ForWriting, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    test_file_contents(testfileW,0,"");

    /* appending to an empty file file and specifying unicode should immediately write a BOM */
    hr = IFileSystem3_OpenTextFile(fs3, name, ForAppending, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    test_file_contents(testfileW,2,L"\ufeff");

    /* appending to a file that contains a BOM should detect unicode mode, but not write a second BOM */
    hr = IFileSystem3_OpenTextFile(fs3, name, ForAppending, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = ITextStream_Write(stream, name);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    lstrcpyW(ExpectedW, L"\ufeff");
    lstrcatW(ExpectedW, name);
    test_file_contents(testfileW,lstrlenW(ExpectedW)*sizeof(WCHAR),ExpectedW);

    hr = IFileSystem3_OpenTextFile(fs3, name, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    b = 10;
    hr = ITextStream_get_AtEndOfStream(stream, &b);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(b == VARIANT_FALSE, "got 0x%x\n", b);
    ITextStream_Release(stream);

    SysFreeString(name);
    DeleteFileW(testfileW);
}

static void test_GetFileVersion(void)
{
    WCHAR pathW[MAX_PATH], filenameW[MAX_PATH];
    BSTR path, version;
    HRESULT hr;

    GetSystemDirectoryW(pathW, ARRAY_SIZE(pathW));

    lstrcpyW(filenameW, pathW);
    lstrcatW(filenameW, L"\\kernel32.dll");

    path = SysAllocString(filenameW);
    hr = IFileSystem3_GetFileVersion(fs3, path, &version);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(*version != 0, "got %s\n", wine_dbgstr_w(version));
    SysFreeString(version);
    SysFreeString(path);

    lstrcpyW(filenameW, pathW);
    lstrcatW(filenameW, L"\\kernel33.dll");

    path = SysAllocString(filenameW);
    version = (void*)0xdeadbeef;
    hr = IFileSystem3_GetFileVersion(fs3, path, &version);
    ok(broken(hr == S_OK) || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), "Unexpected hr %#lx.\n", hr);
    if (hr == S_OK)
    {
        ok(*version == 0, "got %s\n", wine_dbgstr_w(version));
        SysFreeString(version);
    }
    else
        ok(version == (void*)0xdeadbeef, "got %p\n", version);
    SysFreeString(path);
}

static void test_GetParentFolderName(void)
{
    static const struct
    {
        const WCHAR *path;
        const WCHAR *result;
    }
    tests[] =
    {
        { NULL, NULL },
        { L"a", NULL },
        { L"a/a/a", L"a/a" },
        { L"a\\a\\a", L"a\\a" },
        { L"a/a//\\\\", L"a" },
        { L"c:\\\\a", L"c:\\" },
        { L"ac:\\a", L"ac:" }
    };

    BSTR path, result;
    HRESULT hr;
    int i;

    hr = IFileSystem3_GetParentFolderName(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    for(i=0; i < ARRAY_SIZE(tests); i++) {
        result = (BSTR)0xdeadbeef;
        path = tests[i].path ? SysAllocString(tests[i].path) : NULL;
        hr = IFileSystem3_GetParentFolderName(fs3, path, &result);
        ok(hr == S_OK, "%d: unexpected hr %#lx.\n", i, hr);
        if(!tests[i].result)
            ok(!result, "%d) result = %s\n", i, wine_dbgstr_w(result));
        else
            ok(!lstrcmpW(result, tests[i].result), "%d) result = %s\n", i, wine_dbgstr_w(result));
        SysFreeString(path);
        SysFreeString(result);
    }
}

static void test_GetFileName(void)
{
    static const struct
    {
        const WCHAR *path;
        const WCHAR *result;
    } tests[] =
    {
        { NULL, NULL },
        { L"a", L"a" },
        { L"a/a.b", L"a.b" },
        { L"a\\", L"a" },
        { L"c:", NULL },
        { L"/\\", NULL }
    };

    BSTR path, result;
    HRESULT hr;
    int i;

    hr = IFileSystem3_GetFileName(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    for(i=0; i < ARRAY_SIZE(tests); i++) {
        result = (BSTR)0xdeadbeef;
        path = tests[i].path ? SysAllocString(tests[i].path) : NULL;
        hr = IFileSystem3_GetFileName(fs3, path, &result);
        ok(hr == S_OK, "%d: unexpected hr %#lx.\n", i, hr);
        if(!tests[i].result)
            ok(!result, "%d) result = %s\n", i, wine_dbgstr_w(result));
        else
            ok(!lstrcmpW(result, tests[i].result), "%d) result = %s\n", i, wine_dbgstr_w(result));
        SysFreeString(path);
        SysFreeString(result);
    }
}

static void test_GetTempName(void)
{
    BSTR result;
    HRESULT hr;

    hr = IFileSystem3_GetTempName(fs3, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
    result = (BSTR)0xdeadbeef;
    hr = IFileSystem3_GetTempName(fs3, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!!wcsstr( result,L".tmp"), "GetTempName returned %s, expected .tmp suffix\n", debugstr_w(result));
    ok(SysStringLen(result) == lstrlenW(result),"GetTempName returned %s, has incorrect string len.\n", debugstr_w(result));
    SysFreeString(result);
}

static void test_GetBaseName(void)
{
    static const struct
    {
        const WCHAR *path;
        const WCHAR *result;
    }
    tests[] =
    {
        { NULL, NULL},
        { L"a", L"a" },
        { L"a/a.b.c", L"a.b" },
        { L"a.b\\", L"a" },
        { L"c:", NULL },
        { L"/\\", NULL },
        { L".a", L"" }
    };

    BSTR path, result;
    HRESULT hr;
    int i;

    hr = IFileSystem3_GetBaseName(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    for(i=0; i < ARRAY_SIZE(tests); i++) {
        result = (BSTR)0xdeadbeef;
        path = tests[i].path ? SysAllocString(tests[i].path) : NULL;
        hr = IFileSystem3_GetBaseName(fs3, path, &result);
        ok(hr == S_OK, "%d, unexpected hr %#lx.\n", i, hr);
        if(!tests[i].result)
            ok(!result, "%d) result = %s\n", i, wine_dbgstr_w(result));
        else
            ok(!lstrcmpW(result, tests[i].result), "%d) result = %s\n", i, wine_dbgstr_w(result));
        SysFreeString(path);
        SysFreeString(result);
    }
}

static void test_GetAbsolutePathName(void)
{
    static const WCHAR dir1[] = L"test_dir1";
    static const WCHAR dir2[] = L"test_dir2";
    static const WCHAR dir_match1[] = L"test_dir*";
    static const WCHAR dir_match2[] = L"test_di*";

    WIN32_FIND_DATAW fdata;
    HANDLE find;
    WCHAR buf[MAX_PATH], buf2[MAX_PATH];
    BSTR path, result;
    HRESULT hr;

    hr = IFileSystem3_GetAbsolutePathName(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_GetAbsolutePathName(fs3, NULL, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetFullPathNameW(L".", MAX_PATH, buf, NULL);
    ok(!lstrcmpiW(buf, result), "result = %s, expected %s\n", wine_dbgstr_w(result), wine_dbgstr_w(buf));
    SysFreeString(result);

    find = FindFirstFileW(dir_match2, &fdata);
    if(find != INVALID_HANDLE_VALUE) {
        skip("GetAbsolutePathName tests\n");
        FindClose(find);
        return;
    }

    path = SysAllocString(dir_match1);
    hr = IFileSystem3_GetAbsolutePathName(fs3, path, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetFullPathNameW(dir_match1, MAX_PATH, buf2, NULL);
    ok(!lstrcmpiW(buf2, result), "result = %s, expected %s\n", wine_dbgstr_w(result), wine_dbgstr_w(buf2));
    SysFreeString(result);

    ok(CreateDirectoryW(dir1, NULL), "CreateDirectory(%s) failed\n", wine_dbgstr_w(dir1));
    hr = IFileSystem3_GetAbsolutePathName(fs3, path, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetFullPathNameW(dir1, MAX_PATH, buf, NULL);
    ok(!lstrcmpiW(buf, result) || broken(!lstrcmpiW(buf2, result)), "result = %s, expected %s\n",
                wine_dbgstr_w(result), wine_dbgstr_w(buf));
    SysFreeString(result);

    ok(CreateDirectoryW(dir2, NULL), "CreateDirectory(%s) failed\n", wine_dbgstr_w(dir2));
    hr = IFileSystem3_GetAbsolutePathName(fs3, path, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    if(!lstrcmpiW(buf, result) || !lstrcmpiW(buf2, result)) {
        ok(!lstrcmpiW(buf, result) || broken(!lstrcmpiW(buf2, result)), "result = %s, expected %s\n",
                wine_dbgstr_w(result), wine_dbgstr_w(buf));
    }else {
        GetFullPathNameW(dir2, MAX_PATH, buf, NULL);
        ok(!lstrcmpiW(buf, result), "result = %s, expected %s\n",
                wine_dbgstr_w(result), wine_dbgstr_w(buf));
    }
    SysFreeString(result);

    SysFreeString(path);
    path = SysAllocString(dir_match2);
    hr = IFileSystem3_GetAbsolutePathName(fs3, path, &result);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetFullPathNameW(dir_match2, MAX_PATH, buf, NULL);
    ok(!lstrcmpiW(buf, result), "result = %s, expected %s\n", wine_dbgstr_w(result), wine_dbgstr_w(buf));
    SysFreeString(result);
    SysFreeString(path);

    RemoveDirectoryW(dir1);
    RemoveDirectoryW(dir2);
}

static void test_GetFile(void)
{
    BSTR path, str;
    WCHAR pathW[MAX_PATH];
    FileAttribute fa;
    VARIANT size;
    DWORD gfa, new_gfa;
    IFile *file;
    HRESULT hr;
    HANDLE hf;
    BOOL ret;
    DATE date;

    get_temp_path(NULL, pathW);

    path = SysAllocString(pathW);
    hr = IFileSystem3_GetFile(fs3, path, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
    hr = IFileSystem3_GetFile(fs3, NULL, &file);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    file = (IFile*)0xdeadbeef;
    hr = IFileSystem3_GetFile(fs3, path, &file);
    ok(!file, "file != NULL\n");
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);

    hf = CreateFileW(pathW, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_READONLY, NULL);
    if(hf == INVALID_HANDLE_VALUE) {
        skip("Can't create temporary file\n");
        SysFreeString(path);
        return;
    }
    CloseHandle(hf);

    hr = IFileSystem3_GetFile(fs3, path, &file);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFile_get_DateCreated(file, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    date = 0.0;
    hr = IFile_get_DateCreated(file, &date);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(date > 0.0, "got %f\n", date);

    hr = IFile_get_DateLastModified(file, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    date = 0.0;
    hr = IFile_get_DateLastModified(file, &date);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(date > 0.0, "got %f\n", date);

    hr = IFile_get_Path(file, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFile_get_Path(file, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpiW(str, pathW), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

#define FILE_ATTR_MASK (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
        FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ARCHIVE | \
        FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_COMPRESSED)

    hr = IFile_get_Attributes(file, &fa);
    gfa = GetFileAttributesW(pathW) & FILE_ATTR_MASK;
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(fa == gfa, "Unexpected flags %#x, expected %lx.\n", fa, gfa);

    hr = IFile_put_Attributes(file, gfa | FILE_ATTRIBUTE_READONLY);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    new_gfa = GetFileAttributesW(pathW) & FILE_ATTR_MASK;
    ok(new_gfa == (gfa|FILE_ATTRIBUTE_READONLY), "Unexpected flags %#lx, expected %lx\n", new_gfa, gfa|FILE_ATTRIBUTE_READONLY);

    hr = IFile_get_Attributes(file, &fa);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(fa == new_gfa, "Unexpected flags %#x, expected %lx.\n", fa, new_gfa);

    hr = IFile_put_Attributes(file, gfa);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    new_gfa = GetFileAttributesW(pathW) & FILE_ATTR_MASK;
    ok(new_gfa == gfa, "Unexpected flags %#lx, expected %lx.\n", new_gfa, gfa);

    hr = IFile_get_Attributes(file, &fa);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(fa == gfa, "Unexpected flags %#x, expected %lx.\n", fa, gfa);

    hr = IFile_get_Size(file, &size);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(V_VT(&size) == VT_I4, "V_VT(&size) = %d, expected VT_I4\n", V_VT(&size));
    ok(V_I4(&size) == 0, "V_I4(&size) = %ld, expected 0\n", V_I4(&size));
    IFile_Release(file);

    hr = IFileSystem3_DeleteFile(fs3, path, FALSE);
    ok(hr == CTL_E_PERMISSIONDENIED || broken(hr == S_OK), "Unexpected hr %#lx.\n", hr);
    if(hr != S_OK) {
        hr = IFileSystem3_DeleteFile(fs3, path, TRUE);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    }
    hr = IFileSystem3_DeleteFile(fs3, path, TRUE);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);

    SysFreeString(path);

    /* try with directory */
    lstrcatW(pathW, L"\\");
    ret = CreateDirectoryW(pathW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    path = SysAllocString(pathW);
    hr = IFileSystem3_GetFile(fs3, path, &file);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);
    SysFreeString(path);

    RemoveDirectoryW(pathW);
}

static inline BOOL create_file(const WCHAR *name)
{
    HANDLE f = CreateFileW(name, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
    CloseHandle(f);
    return f != INVALID_HANDLE_VALUE;
}

static inline void create_path(const WCHAR *folder, const WCHAR *name, WCHAR *ret)
{
    DWORD len = lstrlenW(folder);
    memmove(ret, folder, len*sizeof(WCHAR));
    ret[len] = '\\';
    memmove(ret+len+1, name, (lstrlenW(name)+1)*sizeof(WCHAR));
}

static void test_CopyFolder(void)
{
    static const WCHAR filesystem3_dir[] = L"filesystem3_test";
    static const WCHAR src1W[] = L"src1";
    static const WCHAR dstW[] = L"dst";

    WCHAR tmp[MAX_PATH];
    BSTR bsrc, bdst;
    HRESULT hr;

    if(!CreateDirectoryW(filesystem3_dir, NULL)) {
        skip("can't create temporary directory\n");
        return;
    }

    create_path(filesystem3_dir, src1W, tmp);
    bsrc = SysAllocString(tmp);
    create_path(filesystem3_dir, dstW, tmp);
    bdst = SysAllocString(tmp);
    hr = IFileSystem3_CopyFile(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_PATHNOTFOUND, "Unexpected hr %#lx.\n", hr);

    ok(create_file(bsrc), "can't create %s file\n", wine_dbgstr_w(bsrc));
    hr = IFileSystem3_CopyFile(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_PATHNOTFOUND, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_DeleteFile(fs3, bsrc, VARIANT_FALSE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    ok(CreateDirectoryW(bsrc, NULL), "can't create %s\n", wine_dbgstr_w(bsrc));
    hr = IFileSystem3_CopyFile(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_DeleteFile(fs3, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    create_path(tmp, src1W, tmp);
    ok(GetFileAttributesW(tmp) == INVALID_FILE_ATTRIBUTES,
            "%s file exists\n", wine_dbgstr_w(tmp));

    create_path(filesystem3_dir, dstW, tmp);
    create_path(tmp, L"", tmp);
    SysFreeString(bdst);
    bdst = SysAllocString(tmp);
    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    create_path(tmp, src1W, tmp);
    ok(GetFileAttributesW(tmp) != INVALID_FILE_ATTRIBUTES,
            "%s directory doesn't exist\n", wine_dbgstr_w(tmp));
    ok(RemoveDirectoryW(tmp), "can't remove %s directory\n", wine_dbgstr_w(tmp));
    create_path(filesystem3_dir, dstW, tmp);
    SysFreeString(bdst);
    bdst = SysAllocString(tmp);

    create_path(filesystem3_dir, L"src*", tmp);
    SysFreeString(bsrc);
    bsrc = SysAllocString(tmp);
    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    create_path(filesystem3_dir, dstW, tmp);
    create_path(tmp, src1W, tmp);
    ok(GetFileAttributesW(tmp) != INVALID_FILE_ATTRIBUTES,
            "%s directory doesn't exist\n", wine_dbgstr_w(tmp));

    hr = IFileSystem3_DeleteFolder(fs3, bdst, VARIANT_FALSE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == CTL_E_PATHNOTFOUND, "Unexpected hr %#lx.\n", hr);

    create_path(filesystem3_dir, src1W, tmp);
    SysFreeString(bsrc);
    bsrc = SysAllocString(tmp);
    create_path(tmp, src1W, tmp);
    ok(create_file(tmp), "can't create %s file\n", wine_dbgstr_w(tmp));
    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_FALSE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_FALSE);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_CopyFolder(fs3, bsrc, bdst, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(bsrc);
    SysFreeString(bdst);

    bsrc = SysAllocString(filesystem3_dir);
    hr = IFileSystem3_DeleteFolder(fs3, bsrc, VARIANT_FALSE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(bsrc);
}

static BSTR bstr_from_str(const char *str)
{
    int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
    BSTR ret = SysAllocStringLen(NULL, len - 1);  /* NUL character added automatically */
    MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
    return ret;
}

struct buildpath_test
{
    const char *path;
    const char *name;
    const char *result;
};

static struct buildpath_test buildpath_data[] =
{
    { "C:\\path", "..\\name.tmp", "C:\\path\\..\\name.tmp" },
    { "C:\\path", "\\name.tmp", "C:\\path\\name.tmp" },
    { "C:\\path", "name.tmp", "C:\\path\\name.tmp" },
    { "C:\\path\\", "name.tmp", "C:\\path\\name.tmp" },
    { "C:\\path", "\\\\name.tmp", "C:\\path\\\\name.tmp" },
    { "C:\\path\\", "\\name.tmp", "C:\\path\\name.tmp" },
    { "C:\\path\\", "\\\\name.tmp", "C:\\path\\\\name.tmp" },
    { "C:\\path\\\\", "\\\\name.tmp", "C:\\path\\\\\\name.tmp" },
    { "C:\\\\", "\\name.tmp", "C:\\\\name.tmp" },
    { "C:", "name.tmp", "C:name.tmp" },
    { "C:", "\\\\name.tmp", "C:\\\\name.tmp" },
    { NULL }
};

static void test_BuildPath(void)
{
    struct buildpath_test *ptr = buildpath_data;
    BSTR ret, path;
    HRESULT hr;
    int i = 0;

    hr = IFileSystem3_BuildPath(fs3, NULL, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    ret = (BSTR)0xdeadbeef;
    hr = IFileSystem3_BuildPath(fs3, NULL, NULL, &ret);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(*ret == 0, "got %p\n", ret);
    SysFreeString(ret);

    ret = (BSTR)0xdeadbeef;
    path = bstr_from_str("path");
    hr = IFileSystem3_BuildPath(fs3, path, NULL, &ret);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(ret, path), "got %s\n", wine_dbgstr_w(ret));
    SysFreeString(ret);
    SysFreeString(path);

    ret = (BSTR)0xdeadbeef;
    path = bstr_from_str("path");
    hr = IFileSystem3_BuildPath(fs3, NULL, path, &ret);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(ret, path), "got %s\n", wine_dbgstr_w(ret));
    SysFreeString(ret);
    SysFreeString(path);

    while (ptr->path)
    {
        BSTR name, result;

        ret = NULL;
        path = bstr_from_str(ptr->path);
        name = bstr_from_str(ptr->name);
        result = bstr_from_str(ptr->result);
        hr = IFileSystem3_BuildPath(fs3, path, name, &ret);
        ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", i, hr);
        if (hr == S_OK)
        {
            ok(!lstrcmpW(ret, result), "%d: got wrong path %s, expected %s\n", i, wine_dbgstr_w(ret),
                wine_dbgstr_w(result));
            SysFreeString(ret);
        }
        SysFreeString(path);
        SysFreeString(name);
        SysFreeString(result);

        i++;
        ptr++;
    }
}

static void test_GetFolder(void)
{
    static const WCHAR dir[] = L"test_dir";

    WCHAR buffW[MAX_PATH], temp_path[MAX_PATH], prev_path[MAX_PATH];
    IFolder *folder;
    HRESULT hr;
    BSTR str;

    folder = (void*)0xdeadbeef;
    hr = IFileSystem3_GetFolder(fs3, NULL, &folder);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    ok(folder == NULL, "got %p\n", folder);

    hr = IFileSystem3_GetFolder(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    /* something that doesn't exist */
    str = SysAllocString(L"dummy");

    hr = IFileSystem3_GetFolder(fs3, str, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    folder = (void*)0xdeadbeef;
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == CTL_E_PATHNOTFOUND, "Unexpected hr %#lx.\n", hr);
    ok(folder == NULL, "got %p\n", folder);
    SysFreeString(str);

    GetWindowsDirectoryW(buffW, MAX_PATH);
    str = SysAllocString(buffW);
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    test_provideclassinfo(folder, &CLSID_Folder);
    IFolder_Release(folder);

    GetCurrentDirectoryW(MAX_PATH, prev_path);
    GetTempPathW(MAX_PATH, temp_path);
    SetCurrentDirectoryW(temp_path);
    ok(CreateDirectoryW(dir, NULL), "CreateDirectory(%s) failed\n", wine_dbgstr_w(dir));
    str = SysAllocString(dir);
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    hr = IFolder_get_Path(folder, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!PathIsRelativeW(str), "path %s is relative.\n", wine_dbgstr_w(str));
    SysFreeString(str);
    IFolder_Release(folder);
    RemoveDirectoryW(dir);
    SetCurrentDirectoryW(prev_path);
}

static void _test_clone(IEnumVARIANT *enumvar, BOOL position_inherited, LONG count, int line)
{
    HRESULT hr;
    IEnumVARIANT *clone;
    ULONG fetched;
    VARIANT var, var2;

    hr = IEnumVARIANT_Reset(enumvar);
    ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);

    VariantInit(&var);
    fetched = -1;
    hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
    ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);
    ok(fetched == 1, "%d: unexpected value %lu.\n", line, fetched);

    /* clone enumerator */
    hr = IEnumVARIANT_Clone(enumvar, &clone);
    ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);
    ok(clone != enumvar, "%d: got %p, %p\n", line, enumvar, clone);

    /* check if clone inherits position */
    VariantInit(&var2);
    fetched = -1;
    hr = IEnumVARIANT_Next(clone, 1, &var2, &fetched);
    if (position_inherited && count == 1)
    {
        ok(hr == S_FALSE, "%d: Unexpected hr %#lx.\n", line, hr);
        ok(!fetched, "%d: unexpected value %lu.\n", line, fetched);
    }
    else
    {
        ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);
        ok(fetched == 1, "%d: unexpected value %lu.\n", line, fetched);
        if (!position_inherited)
            todo_wine ok(V_DISPATCH(&var) == V_DISPATCH(&var2), "%d: values don't match\n", line);
        else
        {
            fetched = -1;
            hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
            ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);
            ok(fetched == 1, "%d: unexpected value %lu.\n", line, fetched);
            todo_wine ok(V_DISPATCH(&var) == V_DISPATCH(&var2), "%d: values don't match\n", line);
        }
    }

    VariantClear(&var2);
    VariantClear(&var);
    IEnumVARIANT_Release(clone);

    hr = IEnumVARIANT_Reset(enumvar);
    ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", line, hr);
}
#define test_clone(a, b, c) _test_clone(a, b, c, __LINE__)

/* Please keep the tests for IFolderCollection and IFileCollection in sync */
static void test_FolderCollection(void)
{
    static const WCHAR aW[] = L"\\a";
    static const WCHAR bW[] = L"\\b";
    static const WCHAR cW[] = L"\\c";
    IFolderCollection *folders;
    WCHAR buffW[MAX_PATH], pathW[MAX_PATH];
    IEnumVARIANT *enumvar;
    LONG count, ref, ref2;
    IUnknown *unk, *unk2;
    IFolder *folder;
    ULONG fetched;
    VARIANT var, var2[2];
    HRESULT hr;
    BSTR str;
    int found_a = 0, found_b = 0, found_c = 0;
    unsigned int i;

    get_temp_path(L"foo", buffW);
    CreateDirectoryW(buffW, NULL);

    str = SysAllocString(buffW);
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    hr = IFolder_get_SubFolders(folder, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFolder_get_Path(folder, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFolder_get_Path(folder, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpiW(buffW, str), "got %s, expected %s\n", wine_dbgstr_w(str), wine_dbgstr_w(buffW));
    SysFreeString(str);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, aW);
    CreateDirectoryW(pathW, NULL);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, bW);
    CreateDirectoryW(pathW, NULL);

    hr = IFolder_get_SubFolders(folder, &folders);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_provideclassinfo(folders, &CLSID_Folders);
    IFolder_Release(folder);

    count = 0;
    hr = IFolderCollection_get_Count(folders, &count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(count == 2, "Unexpected value %lu.\n", count);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, cW);
    CreateDirectoryW(pathW, NULL);

    /* every time property is requested it scans directory */
    count = 0;
    hr = IFolderCollection_get_Count(folders, &count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(count == 3, "Unexpected value %lu.\n", count);

    hr = IFolderCollection_get__NewEnum(folders, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFolderCollection_QueryInterface(folders, &IID_IEnumVARIANT, (void**)&unk);
    ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr);

    /* NewEnum creates new instance each time it's called */
    ref = GET_REFCOUNT(folders);

    unk = NULL;
    hr = IFolderCollection_get__NewEnum(folders, &unk);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    ref2 = GET_REFCOUNT(folders);
    ok(ref2 == ref + 1, "Unexpected value %ld, %ld.\n", ref2, ref);

    unk2 = NULL;
    hr = IFolderCollection_get__NewEnum(folders, &unk2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(unk != unk2, "got %p, %p\n", unk2, unk);
    IUnknown_Release(unk2);

    /* now get IEnumVARIANT */
    ref = GET_REFCOUNT(folders);
    hr = IUnknown_QueryInterface(unk, &IID_IEnumVARIANT, (void**)&enumvar);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ref2 = GET_REFCOUNT(folders);
    ok(ref2 == ref, "Unexpected value %ld, %ld.\n", ref2, ref);

    test_clone(enumvar, FALSE, count);

    for (i = 0; i < 3; i++)
    {
        VariantInit(&var);
        fetched = 0;
        hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
        ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", i, hr);
        ok(fetched == 1, "%d: unexpected value %lu.\n", i, fetched);
        ok(V_VT(&var) == VT_DISPATCH, "%d: got type %d\n", i, V_VT(&var));

        hr = IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IFolder, (void**)&folder);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        str = NULL;
        hr = IFolder_get_Name(folder, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (!lstrcmpW(str, aW + 1))
            found_a++;
        else if (!lstrcmpW(str, bW + 1))
            found_b++;
        else if (!lstrcmpW(str, cW + 1))
            found_c++;
        else
            ok(0, "unexpected folder %s was found\n", wine_dbgstr_w(str));
        SysFreeString(str);

        IFolder_Release(folder);
        VariantClear(&var);
    }

    ok(found_a == 1 && found_b == 1 && found_c == 1,
       "each folder should be found 1 time instead of %d/%d/%d\n",
       found_a, found_b, found_c);

    VariantInit(&var);
    fetched = -1;
    hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
    ok(!fetched, "Unexpected value %lu.\n", fetched);

    hr = IEnumVARIANT_Reset(enumvar);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IEnumVARIANT_Skip(enumvar, 2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IEnumVARIANT_Skip(enumvar, 0);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    VariantInit(&var2[0]);
    VariantInit(&var2[1]);
    fetched = -1;
    hr = IEnumVARIANT_Next(enumvar, 0, var2, &fetched);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!fetched, "Unexpected value %lu.\n", fetched);
    fetched = -1;
    hr = IEnumVARIANT_Next(enumvar, 2, var2, &fetched);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
    ok(fetched == 1, "Unexpected value %lu.\n", fetched);
    ok(V_VT(&var2[0]) == VT_DISPATCH, "got type %d\n", V_VT(&var2[0]));
    VariantClear(&var2[0]);
    VariantClear(&var2[1]);

    IEnumVARIANT_Release(enumvar);
    IUnknown_Release(unk);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, aW);
    RemoveDirectoryW(pathW);
    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, bW);
    RemoveDirectoryW(pathW);
    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, cW);
    RemoveDirectoryW(pathW);
    RemoveDirectoryW(buffW);

    IFolderCollection_Release(folders);
}

/* Please keep the tests for IFolderCollection and IFileCollection in sync */
static void test_FileCollection(void)
{
    static const WCHAR aW[] = L"\\a";
    static const WCHAR bW[] = L"\\b";
    static const WCHAR cW[] = L"\\c";
    WCHAR buffW[MAX_PATH], pathW[MAX_PATH];
    IFolder *folder;
    IFileCollection *files;
    IFile *file;
    IEnumVARIANT *enumvar;
    LONG count, ref, ref2;
    IUnknown *unk, *unk2;
    ULONG fetched;
    VARIANT var, var2[2];
    HRESULT hr;
    BSTR str;
    HANDLE file_a, file_b, file_c;
    int found_a = 0, found_b = 0, found_c = 0;
    unsigned int i;

    get_temp_path(L"\\foo", buffW);
    CreateDirectoryW(buffW, NULL);

    str = SysAllocString(buffW);
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    hr = IFolder_get_Files(folder, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, aW);
    file_a = CreateFileW(pathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                         FILE_FLAG_DELETE_ON_CLOSE, 0);
    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, bW);
    file_b = CreateFileW(pathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                         FILE_FLAG_DELETE_ON_CLOSE, 0);

    hr = IFolder_get_Files(folder, &files);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_provideclassinfo(files, &CLSID_Files);
    IFolder_Release(folder);

    count = 0;
    hr = IFileCollection_get_Count(files, &count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(count == 2, "Unexpected value %lu.\n", count);

    lstrcpyW(pathW, buffW);
    lstrcatW(pathW, cW);
    file_c = CreateFileW(pathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                         FILE_FLAG_DELETE_ON_CLOSE, 0);

    /* every time property is requested it scans directory */
    count = 0;
    hr = IFileCollection_get_Count(files, &count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(count == 3, "Unexpected value %lu.\n", count);

    hr = IFileCollection_get__NewEnum(files, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFileCollection_QueryInterface(files, &IID_IEnumVARIANT, (void**)&unk);
    ok(hr == E_NOINTERFACE, "Unexpected hr %#lx.\n", hr);

    /* NewEnum creates new instance each time it's called */
    ref = GET_REFCOUNT(files);

    unk = NULL;
    hr = IFileCollection_get__NewEnum(files, &unk);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    ref2 = GET_REFCOUNT(files);
    ok(ref2 == ref + 1, "Unexpected value %ld, %ld.\n", ref2, ref);

    unk2 = NULL;
    hr = IFileCollection_get__NewEnum(files, &unk2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(unk != unk2, "got %p, %p\n", unk2, unk);
    IUnknown_Release(unk2);

    /* now get IEnumVARIANT */
    ref = GET_REFCOUNT(files);
    hr = IUnknown_QueryInterface(unk, &IID_IEnumVARIANT, (void**)&enumvar);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ref2 = GET_REFCOUNT(files);
    ok(ref2 == ref, "Unexpected value %ld, %ld.\n", ref2, ref);

    test_clone(enumvar, FALSE, count);

    for (i = 0; i < 3; i++)
    {
        VariantInit(&var);
        fetched = 0;
        hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
        ok(hr == S_OK, "%d: Unexpected hr %#lx.\n", i, hr);
        ok(fetched == 1, "%d: unexpected value %lu.\n", i, fetched);
        ok(V_VT(&var) == VT_DISPATCH, "%d: got type %d\n", i, V_VT(&var));

        hr = IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IFile, (void **)&file);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        test_provideclassinfo(file, &CLSID_File);

        str = NULL;
        hr = IFile_get_Name(file, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (!lstrcmpW(str, aW + 1))
            found_a++;
        else if (!lstrcmpW(str, bW + 1))
            found_b++;
        else if (!lstrcmpW(str, cW + 1))
            found_c++;
        else
            ok(0, "unexpected file %s was found\n", wine_dbgstr_w(str));
        SysFreeString(str);

        IFile_Release(file);
        VariantClear(&var);
    }

    ok(found_a == 1 && found_b == 1 && found_c == 1,
       "each file should be found 1 time instead of %d/%d/%d\n",
       found_a, found_b, found_c);

    VariantInit(&var);
    fetched = -1;
    hr = IEnumVARIANT_Next(enumvar, 1, &var, &fetched);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
    ok(!fetched, "Unexpected value %lu.\n", fetched);

    hr = IEnumVARIANT_Reset(enumvar);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IEnumVARIANT_Skip(enumvar, 2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IEnumVARIANT_Skip(enumvar, 0);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    VariantInit(&var2[0]);
    VariantInit(&var2[1]);
    fetched = 1;
    hr = IEnumVARIANT_Next(enumvar, 0, var2, &fetched);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!fetched, "Unexpected value %lu.\n", fetched);
    fetched = 0;
    hr = IEnumVARIANT_Next(enumvar, 2, var2, &fetched);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);
    ok(fetched == 1, "Unexpected value %lu.\n", fetched);
    ok(V_VT(&var2[0]) == VT_DISPATCH, "got type %d\n", V_VT(&var2[0]));
    VariantClear(&var2[0]);
    VariantClear(&var2[1]);

    IEnumVARIANT_Release(enumvar);
    IUnknown_Release(unk);

    CloseHandle(file_a);
    CloseHandle(file_b);
    CloseHandle(file_c);
    RemoveDirectoryW(buffW);

    IFileCollection_Release(files);
}

static void test_DriveCollection(void)
{
    IDriveCollection *drives;
    IEnumVARIANT *enumvar;
    ULONG fetched;
    VARIANT var;
    HRESULT hr;
    LONG count;

    hr = IFileSystem3_get_Drives(fs3, &drives);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    test_provideclassinfo(drives, &CLSID_Drives);

    hr = IDriveCollection_get__NewEnum(drives, (IUnknown**)&enumvar);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IDriveCollection_get_Count(drives, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    count = 0;
    hr = IDriveCollection_get_Count(drives, &count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(count > 0, "Unexpected value %ld.\n", count);

    V_VT(&var) = VT_EMPTY;
    fetched = 1;
    hr = IEnumVARIANT_Next(enumvar, 0, &var, &fetched);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(!fetched, "Unexpected value %lu.\n", fetched);

    hr = IEnumVARIANT_Skip(enumvar, 0);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IEnumVARIANT_Skip(enumvar, count);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = IEnumVARIANT_Skip(enumvar, 1);
    ok(hr == S_FALSE, "Unexpected hr %#lx.\n", hr);

    test_clone(enumvar, TRUE, count);

    while (IEnumVARIANT_Next(enumvar, 1, &var, &fetched) == S_OK) {
        IDrive *drive = (IDrive*)V_DISPATCH(&var);
        DriveTypeConst type;
        BSTR str;

        hr = IDrive_get_DriveType(drive, &type);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        hr = IDrive_get_DriveLetter(drive, NULL);
        ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

        hr = IDrive_get_DriveLetter(drive, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(SysStringLen(str) == 1, "got string %s\n", wine_dbgstr_w(str));
        SysFreeString(str);

        hr = IDrive_get_IsReady(drive, NULL);
        ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

        hr = IDrive_get_TotalSize(drive, NULL);
        ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

        hr = IDrive_get_AvailableSpace(drive, NULL);
        ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

        hr = IDrive_get_FreeSpace(drive, NULL);
        ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

        if (type == Fixed) {
            VARIANT_BOOL ready = VARIANT_FALSE;
            VARIANT size;

            hr = IDrive_get_IsReady(drive, &ready);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(ready == VARIANT_TRUE, "got %x\n", ready);

            if (ready != VARIANT_TRUE) {
                hr = IDrive_get_DriveLetter(drive, &str);
                ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

                skip("Drive %s is not ready, skipping some tests\n", wine_dbgstr_w(str));

                VariantClear(&var);
                SysFreeString(str);
                continue;
            }

            V_VT(&size) = VT_EMPTY;
            hr = IDrive_get_TotalSize(drive, &size);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(V_VT(&size) == VT_R8 || V_VT(&size) == VT_I4, "got %d\n", V_VT(&size));
            if (V_VT(&size) == VT_R8)
                ok(V_R8(&size) > 0, "got %f\n", V_R8(&size));
            else
                ok(V_I4(&size) > 0, "got %ld\n", V_I4(&size));

            V_VT(&size) = VT_EMPTY;
            hr = IDrive_get_AvailableSpace(drive, &size);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(V_VT(&size) == VT_R8 || V_VT(&size) == VT_I4, "got %d\n", V_VT(&size));
            if (V_VT(&size) == VT_R8)
                ok(V_R8(&size) > (double)INT_MAX, "got %f\n", V_R8(&size));
            else
                ok(V_I4(&size) > 0, "got %ld\n", V_I4(&size));

            V_VT(&size) = VT_EMPTY;
            hr = IDrive_get_FreeSpace(drive, &size);
            ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
            ok(V_VT(&size) == VT_R8 || V_VT(&size) == VT_I4, "got %d\n", V_VT(&size));
            if (V_VT(&size) == VT_R8)
                ok(V_R8(&size) > 0, "got %f\n", V_R8(&size));
            else
                ok(V_I4(&size) > 0, "got %ld\n", V_I4(&size));
        }
        VariantClear(&var);
    }

    IEnumVARIANT_Release(enumvar);
    IDriveCollection_Release(drives);
}

static void get_temp_filepath(const WCHAR *filename, WCHAR *path, WCHAR *dir)
{
    GetTempPathW(MAX_PATH, path);
    lstrcatW(path, L"scrrun\\");
    lstrcpyW(dir, path);
    lstrcatW(path, filename);
}

static void test_CreateTextFile(void)
{
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH];
    ITextStream *stream;
    BSTR nameW, str;
    HANDLE file;
    HRESULT hr;
    BOOL ret;

    get_temp_filepath(testfileW, pathW, dirW);

    /* dir doesn't exist */
    nameW = SysAllocString(pathW);
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == CTL_E_PATHNOTFOUND, "Unexpected hr %#lx.\n", hr);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    test_provideclassinfo(stream, &CLSID_TextStream);

    hr = ITextStream_Read(stream, 1, &str);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Close(stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Read(stream, 1, &str);
    ok(hr == CTL_E_BADFILEMODE || hr == E_VAR_NOT_SET, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Close(stream);
    ok(hr == S_FALSE || hr == E_VAR_NOT_SET, "Unexpected hr %#lx.\n", hr);

    ITextStream_Release(stream);

    /* check it's created */
    file = CreateFileW(pathW, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    ok(file != INVALID_HANDLE_VALUE, "got %p\n", file);
    CloseHandle(file);

    /* try to create again with no-overwrite mode */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);

    /* now overwrite */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    /* overwrite in Unicode mode, check for BOM */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_TRUE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    test_file_contents(pathW,2,L"\ufeff");

    DeleteFileW(nameW);
    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static void test_FolderCreateTextFile(void)
{
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH];
    ITextStream *stream;
    BSTR nameW, str;
    HANDLE file;
    IFolder *folder;
    HRESULT hr;
    BOOL ret;

    get_temp_filepath(testfileW, pathW, dirW);
    nameW = SysAllocString(L"foo.txt");
    lstrcpyW(pathW, dirW);
    lstrcatW(pathW, nameW);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    str = SysAllocString(dirW);
    hr = IFileSystem3_GetFolder(fs3, str, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    hr = IFolder_CreateTextFile(folder, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    test_provideclassinfo(stream, &CLSID_TextStream);

    hr = ITextStream_Read(stream, 1, &str);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Close(stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Read(stream, 1, &str);
    ok(hr == CTL_E_BADFILEMODE || hr == E_VAR_NOT_SET, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Close(stream);
    ok(hr == S_FALSE || hr == E_VAR_NOT_SET, "Unexpected hr %#lx.\n", hr);

    ITextStream_Release(stream);

    /* check it's created */
    file = CreateFileW(pathW, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    ok(file != INVALID_HANDLE_VALUE, "got %p\n", file);
    CloseHandle(file);

    /* try to create again with no-overwrite mode */
    hr = IFolder_CreateTextFile(folder, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);

    /* now overwrite */
    hr = IFolder_CreateTextFile(folder, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    /* overwrite in Unicode mode, check for BOM */
    hr = IFolder_CreateTextFile(folder, nameW, VARIANT_TRUE, VARIANT_TRUE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    test_file_contents(pathW,2,L"\ufeff");

    DeleteFileW(pathW);
    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static void test_WriteLine(void)
{
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH];
    WCHAR ExpectedW[MAX_PATH];
    char ExpectedA[MAX_PATH];
    ITextStream *stream;
    DWORD len;
    BSTR nameW;
    HRESULT hr;
    BOOL ret;

    get_temp_filepath(testfileW, pathW, dirW);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    /* create as ASCII file first */
    nameW = SysAllocString(pathW);
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_WriteLine(stream, nameW);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    len = WideCharToMultiByte(CP_ACP, 0, nameW, -1, ExpectedA, ARRAY_SIZE(ExpectedA)-2, NULL, NULL);
    ok(len > 0, "got %ld", len);
    lstrcatA(ExpectedA, "\r\n");
    test_file_contents(pathW,lstrlenA(ExpectedA),ExpectedA);
    DeleteFileW(nameW);

    /* same for unicode file */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_TRUE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_WriteLine(stream, nameW);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ITextStream_Release(stream);

    lstrcpyW(ExpectedW, L"\ufeff");
    lstrcatW(ExpectedW, nameW);
    lstrcatW(ExpectedW, L"\r\n");
    test_file_contents(pathW,lstrlenW(ExpectedW)*sizeof(WCHAR),ExpectedW);
    DeleteFileW(nameW);

    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static HRESULT write_blank_lines(WCHAR *nameW, VARIANT_BOOL is_unicode, LONG lines)
{
    HRESULT hr;
    ITextStream *stream;

    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, is_unicode, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = ITextStream_WriteBlankLines(stream, lines);
    ITextStream_Release(stream);
    return hr;
}

static void test_WriteBlankLines(void)
{
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH];
    BSTR nameW;
    HRESULT hr;
    BOOL ret;

    get_temp_filepath(testfileW, pathW, dirW);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    nameW = SysAllocString(pathW);

    hr = write_blank_lines(nameW, VARIANT_FALSE, -1);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 0, NULL);
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_FALSE, 0);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 0, NULL);
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_FALSE, 1);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 2, "\r\n");
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_FALSE, 2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 4, "\r\n\r\n");
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_TRUE, -1);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 1*sizeof(WCHAR), L"\ufeff");
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_TRUE, 0);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 1*sizeof(WCHAR), L"\ufeff");
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_TRUE, 1);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 3*sizeof(WCHAR), L"\ufeff\r\n");
    DeleteFileW(nameW);

    hr = write_blank_lines(nameW, VARIANT_TRUE, 2);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    test_file_contents(pathW, 5*sizeof(WCHAR), L"\ufeff\r\n\r\n");
    DeleteFileW(nameW);

    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static void test_ReadAll(void)
{
    static const WCHAR firstlineW[] = L"first";
    static const WCHAR secondlineW[] = L"second";
    static const WCHAR aW[] = L"A";
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH], buffW[500];
    char buffA[MAX_PATH];
    ITextStream *stream;
    BSTR nameW;
    HRESULT hr;
    BOOL ret;
    BSTR str;

    get_temp_filepath(testfileW, pathW, dirW);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    /* Unicode file -> read with ascii stream */
    nameW = SysAllocString(pathW);
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_TRUE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(firstlineW);
    hr = ITextStream_WriteLine(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    str = SysAllocString(secondlineW);
    hr = ITextStream_WriteLine(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    hr = ITextStream_ReadAll(stream, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL || broken(str == (void*)0xdeadbeef) /* win2k */, "got %p\n", str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_ReadAll(stream, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    /* Buffer content is not interpreted - BOM is kept, all data is converted to WCHARs */
    str = NULL;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2k */, "Unexpected hr %#lx.\n", hr);
    buffW[0] = 0;
    lstrcpyA(buffA, utf16bom);
    lstrcatA(buffA, "first");
    MultiByteToWideChar(CP_ACP, 0, buffA, -1, buffW, ARRAY_SIZE(buffW));
    ok(str[0] == buffW[0] && str[1] == buffW[1], "got %s, %d\n", wine_dbgstr_w(str), SysStringLen(str));
    SysFreeString(str);
    ITextStream_Release(stream);

    /* Unicode file -> read with unicode stream */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    lstrcpyW(buffW, firstlineW);
    lstrcatW(buffW, L"\r\n");
    lstrcatW(buffW, secondlineW);
    lstrcatW(buffW, L"\r\n");
    str = NULL;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2k */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    /* ReadAll one more time */
    str = (void*)0xdeadbeef;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL || broken(str == (void*)0xdeadbeef) /* win2k */, "got %p\n", str);

    /* ReadLine fails the same way */
    str = (void*)0xdeadbeef;
    hr = ITextStream_ReadLine(stream, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL || broken(str == (void*)0xdeadbeef) /* win2k */, "got %p\n", str);
    ITextStream_Release(stream);

    /* Open again and skip first line before ReadAll */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = NULL;
    hr = ITextStream_ReadLine(stream, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(str != NULL, "got %p\n", str);
    ok(!wcscmp(str, firstlineW), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    lstrcpyW(buffW, secondlineW);
    lstrcatW(buffW, L"\r\n");
    str = NULL;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2k */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);
    ITextStream_Release(stream);

    /* ASCII file, read with Unicode stream */
    /* 1. one byte content, not enough for Unicode read */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    str = SysAllocString(aW);
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_ReadAll(stream, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL || broken(str == (void*)0xdeadbeef) /* win2k */, "got %p\n", str);

    ITextStream_Release(stream);

    DeleteFileW(nameW);
    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static void test_Read(void)
{
    static const WCHAR firstlineW[] = L"first";
    static const WCHAR secondlineW[] = L"second";
    WCHAR pathW[MAX_PATH], dirW[MAX_PATH], buffW[500];
    char buffA[MAX_PATH];
    ITextStream *stream;
    BSTR nameW;
    HRESULT hr;
    BOOL ret;
    BSTR str;

    get_temp_filepath(testfileW, pathW, dirW);

    ret = CreateDirectoryW(dirW, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    /* Unicode file -> read with ascii stream */
    nameW = SysAllocString(pathW);
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_FALSE, VARIANT_TRUE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(firstlineW);
    hr = ITextStream_WriteLine(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    str = SysAllocString(secondlineW);
    hr = ITextStream_WriteLine(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    hr = ITextStream_Read(stream, 0, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Read(stream, 1, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Read(stream, -1, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 1, &str);
    ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    hr = ITextStream_Read(stream, 1, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, -1, &str);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 0, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    /* Buffer content is not interpreted - BOM is kept, all data is converted to WCHARs */
    str = NULL;
    hr = ITextStream_Read(stream, 2, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    buffW[0] = 0;
    lstrcpyA(buffA, utf16bom);
    lstrcatA(buffA, "first");
    MultiByteToWideChar(CP_ACP, 0, buffA, -1, buffW, ARRAY_SIZE(buffW));
    ok(str[0] == buffW[0] && str[1] == buffW[1], "got %s, expected %s, %d\n", wine_dbgstr_w(str), wine_dbgstr_w(buffW), SysStringLen(str));
    ok(SysStringLen(str) == 2, "got %d\n", SysStringLen(str));
    SysFreeString(str);
    ITextStream_Release(stream);

    /* Unicode file -> read with unicode stream */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    lstrcpyW(buffW, firstlineW);
    lstrcatW(buffW, L"\r\n");
    lstrcatW(buffW, secondlineW);
    lstrcatW(buffW, L"\r\n");
    str = NULL;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2k */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    /* ReadAll one more time */
    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 10, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    /* ReadLine fails the same way */
    str = (void*)0xdeadbeef;
    hr = ITextStream_ReadLine(stream, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL || broken(str == (void*)0xdeadbeef), "got %p\n", str);
    ITextStream_Release(stream);

    /* Open again and skip first line before ReadAll */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = NULL;
    hr = ITextStream_ReadLine(stream, &str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(str != NULL, "got %p\n", str);
    SysFreeString(str);

    lstrcpyW(buffW, secondlineW);
    lstrcatW(buffW, L"\r\n");
    str = NULL;
    hr = ITextStream_Read(stream, 100, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2k */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);
    ITextStream_Release(stream);

    /* default read will use Unicode */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    lstrcpyW(buffW, firstlineW);
    lstrcatW(buffW, L"\r\n");
    lstrcatW(buffW, secondlineW);
    lstrcatW(buffW, L"\r\n");
    str = NULL;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    ITextStream_Release(stream);

    /* default append will use Unicode */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForAppending, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(L"123");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    lstrcatW(buffW, L"123");
    str = NULL;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(buffW, str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    ITextStream_Release(stream);

    /* default write will use ASCII */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForWriting, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(L"123");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!wcscmp(str, L"123"), "got %s\n", wine_dbgstr_w(str));

    ITextStream_Release(stream);
    /* ASCII file, read with default stream */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    str = SysAllocString(L"test");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!wcscmp(str, L"test"), "got %s\n", wine_dbgstr_w(str));

    ITextStream_Release(stream);

    /* default append will use Unicode */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForAppending, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(L"123");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = NULL;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!lstrcmpW(L"test123", str), "got %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    ITextStream_Release(stream);

    /* default write will use ASCII as well */
    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForWriting, VARIANT_FALSE, TristateUseDefault, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = SysAllocString(L"test string");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(!wcscmp(str, L"test string"), "got %s\n", wine_dbgstr_w(str));

    ITextStream_Release(stream);

    /* ASCII file, read with Unicode stream */
    /* 1. one byte content, not enough for Unicode read */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    str = SysAllocString(L"A");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    ITextStream_Release(stream);

    /* ASCII file, read with Unicode stream */
    /* 3. one byte content, 2 are interpreted as a character, 3rd is lost */
    hr = IFileSystem3_CreateTextFile(fs3, nameW, VARIANT_TRUE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    str = SysAllocString(L"abc");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    ITextStream_Release(stream);

    hr = IFileSystem3_OpenTextFile(fs3, nameW, ForReading, VARIANT_FALSE, TristateTrue, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    str = NULL;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win2003 */, "Unexpected hr %#lx.\n", hr);
    ok(SysStringLen(str) == 1, "len = %u\n", SysStringLen(str));
    SysFreeString(str);

    str = (void*)0xdeadbeef;
    hr = ITextStream_Read(stream, 500, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(str == NULL, "got %p\n", str);

    ITextStream_Release(stream);

    DeleteFileW(nameW);
    RemoveDirectoryW(dirW);
    SysFreeString(nameW);
}

static void test_ReadLine(void)
{
    WCHAR path[MAX_PATH], dir[MAX_PATH];
    ITextStream *stream;
    unsigned int i;
    HANDLE file;
    DWORD size;
    HRESULT hr;
    BSTR str;
    BOOL ret;

    const char data[] = "first line\r\nsecond\n\n\rt\r\re \rst\n";

    get_temp_filepath(L"test.txt", path, dir);

    ret = CreateDirectoryW(dir, NULL);
    ok(ret, "Unexpected retval %d, error %ld.\n", ret, GetLastError());

    file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL, NULL);
    ok(file != INVALID_HANDLE_VALUE, "CreateFile failed\n");

    for (i = 0; i < 1000; i++)
        WriteFile(file, data, strlen(data), &size, NULL);
    CloseHandle(file);

    str = SysAllocString(path);
    hr = IFileSystem3_OpenTextFile(fs3, str, ForReading, VARIANT_FALSE, TristateFalse, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    for (i = 0; i < 1000; i++)
    {
        hr = ITextStream_ReadLine(stream, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(!wcscmp(str, L"first line"), "ReadLine returned %s\n", wine_dbgstr_w(str));
        SysFreeString(str);

        hr = ITextStream_ReadLine(stream, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(!wcscmp(str, L"second"), "ReadLine returned %s\n", wine_dbgstr_w(str));
        SysFreeString(str);

        hr = ITextStream_ReadLine(stream, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(!*str, "ReadLine returned %s\n", wine_dbgstr_w(str));
        SysFreeString(str);

        hr = ITextStream_ReadLine(stream, &str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        ok(!wcscmp(str, L"\rt\r\re \rst"), "ReadLine returned %s\n", wine_dbgstr_w(str));
        SysFreeString(str);
    }

    str = NULL;
    hr = ITextStream_ReadLine(stream, &str);
    ok(hr == CTL_E_ENDOFFILE, "Unexpected hr %#lx.\n", hr);
    ok(!str, "ReadLine returned %s\n", wine_dbgstr_w(str));
    SysFreeString(str);

    ITextStream_Release(stream);

    ret = DeleteFileW(path);
    ok(ret, "Unexpected retval %ld.\n", GetLastError());

    ret = RemoveDirectoryW(dir);
    ok(ret, "Unexpected retval %ld.\n", GetLastError());
}

struct driveexists_test {
    const WCHAR drivespec[10];
    const INT drivetype;
    const VARIANT_BOOL expected_ret;
};

/* If 'drivetype' != -1, the first character of 'drivespec' will be replaced
 * with the drive letter of a drive of this type. If such a drive does not exist,
 * the test will be skipped. */
static const struct driveexists_test driveexiststestdata[] = {
    { L"N:\\", DRIVE_NO_ROOT_DIR, VARIANT_FALSE },
    { L"R:\\", DRIVE_REMOVABLE, VARIANT_TRUE },
    { L"F:\\", DRIVE_FIXED, VARIANT_TRUE },
    { L"F:", DRIVE_FIXED, VARIANT_TRUE },
    { L"F?", DRIVE_FIXED, VARIANT_FALSE },
    { L"F", DRIVE_FIXED, VARIANT_TRUE },
    { L"?", -1, VARIANT_FALSE },
    { L"" }
};

static void test_DriveExists(void)
{
    const struct driveexists_test *ptr = driveexiststestdata;
    HRESULT hr;
    VARIANT_BOOL ret;
    BSTR drivespec;
    WCHAR root[] = L"?:\\";

    hr = IFileSystem3_DriveExists(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    ret = VARIANT_TRUE;
    hr = IFileSystem3_DriveExists(fs3, NULL, &ret);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(ret == VARIANT_FALSE, "got %x\n", ret);

    drivespec = SysAllocString(root);
    hr = IFileSystem3_DriveExists(fs3, drivespec, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
    SysFreeString(drivespec);

    for (; *ptr->drivespec; ptr++) {
        drivespec = SysAllocString(ptr->drivespec);
        if (ptr->drivetype != -1) {
            for (root[0] = 'A'; root[0] <= 'Z'; root[0]++)
                if (GetDriveTypeW(root) == ptr->drivetype)
                    break;
            if (root[0] > 'Z') {
                skip("No drive with type 0x%x found, skipping test %s.\n",
                        ptr->drivetype, wine_dbgstr_w(ptr->drivespec));
                SysFreeString(drivespec);
                continue;
            }

            /* Test both upper and lower case drive letters. */
            drivespec[0] = root[0];
            ret = ptr->expected_ret == VARIANT_TRUE ? VARIANT_FALSE : VARIANT_TRUE;
            hr = IFileSystem3_DriveExists(fs3, drivespec, &ret);
            ok(hr == S_OK, "Unexpected hr %#lx. for drive spec %s (%s)\n",
                    hr, wine_dbgstr_w(drivespec), wine_dbgstr_w(ptr->drivespec));
            ok(ret == ptr->expected_ret, "got %d, expected %d for drive spec %s (%s)\n",
                    ret, ptr->expected_ret, wine_dbgstr_w(drivespec), wine_dbgstr_w(ptr->drivespec));

            drivespec[0] = tolower(root[0]);
        }

        ret = ptr->expected_ret == VARIANT_TRUE ? VARIANT_FALSE : VARIANT_TRUE;
        hr = IFileSystem3_DriveExists(fs3, drivespec, &ret);
        ok(hr == S_OK, "Unexpected hr %#lx. for drive spec %s (%s)\n",
                hr, wine_dbgstr_w(drivespec), wine_dbgstr_w(ptr->drivespec));
        ok(ret == ptr->expected_ret, "got %d, expected %d for drive spec %s (%s)\n",
                ret, ptr->expected_ret, wine_dbgstr_w(drivespec), wine_dbgstr_w(ptr->drivespec));

        SysFreeString(drivespec);
    }
}

struct getdrivename_test {
    const WCHAR path[10];
    const WCHAR drive[5];
};

static const struct getdrivename_test getdrivenametestdata[] = {
    { L"C:\\1.tst", L"C:" },
    { L"O:\\1.tst", L"O:" },
    { L"O:", L"O:" },
    { L"o:", L"o:" },
    { L"OO:" },
    { L":" },
    { L"O" },
    { L"" }
};

static void test_GetDriveName(void)
{
    const struct getdrivename_test *ptr = getdrivenametestdata;
    HRESULT hr;
    BSTR name;

    hr = IFileSystem3_GetDriveName(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    name = (void*)0xdeadbeef;
    hr = IFileSystem3_GetDriveName(fs3, NULL, &name);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(name == NULL, "got %p\n", name);

    while (*ptr->path) {
        BSTR path = SysAllocString(ptr->path);
        name = (void*)0xdeadbeef;
        hr = IFileSystem3_GetDriveName(fs3, path, &name);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (name)
            ok(!lstrcmpW(ptr->drive, name), "got %s, expected %s\n", wine_dbgstr_w(name), wine_dbgstr_w(ptr->drive));
        else
            ok(!*ptr->drive, "got %s, expected %s\n", wine_dbgstr_w(name), wine_dbgstr_w(ptr->drive));
        SysFreeString(path);
        SysFreeString(name);
        ptr++;
    }
}

struct getdrive_test {
    const WCHAR drivespec[12];
    HRESULT res;
    const WCHAR driveletter[2];
};

static void test_GetDrive(void)
{
    HRESULT hr;
    IDrive *drive_fixed, *drive;
    BSTR dl_fixed, drivespec;
    WCHAR root[] = L"?:\\";

    drive = (void*)0xdeadbeef;
    hr = IFileSystem3_GetDrive(fs3, NULL, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
    ok(drive == (void*)0xdeadbeef, "got %p\n", drive);

    for (root[0] = 'A'; root[0] <= 'Z'; root[0]++)
        if (GetDriveTypeW(root) == DRIVE_NO_ROOT_DIR)
            break;

    if (root[0] > 'Z')
        skip("All drive letters are occupied, skipping test for nonexisting drive.\n");
    else {
        drivespec = SysAllocString(root);
        drive = (void*)0xdeadbeef;
        hr = IFileSystem3_GetDrive(fs3, drivespec, &drive);
        ok(hr == CTL_E_DEVICEUNAVAILABLE, "Unexpected hr %#lx.\n", hr);
        ok(drive == NULL, "got %p\n", drive);
        SysFreeString(drivespec);
    }

    drive_fixed = get_fixed_drive();
    if (!drive_fixed) {
        skip("No fixed drive found, skipping test.\n");
        return;
    }

    hr = IDrive_get_DriveLetter(drive_fixed, &dl_fixed);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    if (FAILED(hr))
        skip("Could not retrieve drive letter of fixed drive, skipping test.\n");
    else {
        WCHAR dl_upper = toupper(dl_fixed[0]);
        WCHAR dl_lower = tolower(dl_fixed[0]);
        struct getdrive_test testdata[] = {
            { {dl_upper,0}, S_OK, {dl_upper,0} },
            { {dl_upper,':',0}, S_OK, {dl_upper,0} },
            { {dl_upper,':','\\',0}, S_OK, {dl_upper,0} },
            { {dl_lower,':','\\',0}, S_OK, {dl_upper,0} },
            { {dl_upper,'\\',0 }, E_INVALIDARG, L""},
            { {dl_lower,'\\',0 }, E_INVALIDARG, L""},
            { L"$:\\", E_INVALIDARG, L"" },
            { L"\\host\\share", E_INVALIDARG, L"" },
            { L"host\\share", E_INVALIDARG, L"" },
            { L"" },
        };
        struct getdrive_test *ptr = &testdata[0];

        for (; *ptr->drivespec; ptr++) {
            drivespec = SysAllocString(ptr->drivespec);
            drive = (void*)0xdeadbeef;
            hr = IFileSystem3_GetDrive(fs3, drivespec, &drive);
            ok(hr == ptr->res, "Unexpected hr %#lx, expected %#lx for drive spec %s.\n",
                    hr, ptr->res, wine_dbgstr_w(ptr->drivespec));
            ok(!lstrcmpW(ptr->drivespec, drivespec), "GetDrive modified its DriveSpec argument\n");
            SysFreeString(drivespec);

            if (*ptr->driveletter) {
                BSTR driveletter;
                hr = IDrive_get_DriveLetter(drive, &driveletter);
                ok(hr == S_OK, "Unexpected hr %#lx. for drive spec %s\n", hr, wine_dbgstr_w(ptr->drivespec));
                if (SUCCEEDED(hr)) {
                    ok(!lstrcmpW(ptr->driveletter, driveletter), "got %s, expected %s for drive spec %s\n",
                            wine_dbgstr_w(driveletter), wine_dbgstr_w(ptr->driveletter),
                            wine_dbgstr_w(ptr->drivespec));
                    SysFreeString(driveletter);
                }
                test_provideclassinfo(drive, &CLSID_Drive);
                IDrive_Release(drive);
            } else
                ok(drive == NULL, "got %p for drive spec %s\n", drive, wine_dbgstr_w(ptr->drivespec));
        }
        SysFreeString(dl_fixed);
    }
}

static void test_SerialNumber(void)
{
    IDrive *drive;
    LONG serial;
    HRESULT hr;
    BSTR name;

    drive = get_fixed_drive();
    if (!drive) {
        skip("No fixed drive found, skipping test.\n");
        return;
    }

    hr = IDrive_get_SerialNumber(drive, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    serial = 0xdeadbeef;
    hr = IDrive_get_SerialNumber(drive, &serial);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(serial != 0xdeadbeef, "Unexpected value %#lx.\n", serial);

    hr = IDrive_get_FileSystem(drive, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    name = NULL;
    hr = IDrive_get_FileSystem(drive, &name);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(name != NULL, "got %p\n", name);
    SysFreeString(name);

    hr = IDrive_get_VolumeName(drive, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    name = NULL;
    hr = IDrive_get_VolumeName(drive, &name);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ok(name != NULL, "got %p\n", name);
    SysFreeString(name);

    IDrive_Release(drive);
}

static const struct extension_test {
    WCHAR path[20];
    WCHAR ext[10];
} extension_tests[] = {
    { L"noext", L"" },
    { L"n.o.ext", L"ext" },
    { L"n.o.eXt", L"eXt" },
    { L"" }
};

static void test_GetExtensionName(void)
{
    BSTR path, ext;
    HRESULT hr;
    int i;

    for (i = 0; i < ARRAY_SIZE(extension_tests); i++) {

       path = SysAllocString(extension_tests[i].path);
       ext = NULL;
       hr = IFileSystem3_GetExtensionName(fs3, path, &ext);
       ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
       if (*extension_tests[i].ext)
           ok(!lstrcmpW(ext, extension_tests[i].ext), "%d: path %s, got %s, expected %s\n", i,
               wine_dbgstr_w(path), wine_dbgstr_w(ext), wine_dbgstr_w(extension_tests[i].ext));
       else
           ok(ext == NULL, "%d: path %s, got %s, expected %s\n", i,
               wine_dbgstr_w(path), wine_dbgstr_w(ext), wine_dbgstr_w(extension_tests[i].ext));

       SysFreeString(path);
       SysFreeString(ext);
    }
}

static void test_GetSpecialFolder(void)
{
    WCHAR pathW[MAX_PATH];
    IFolder *folder;
    HRESULT hr;
    DWORD ret;
    BSTR path;

    hr = IFileSystem3_GetSpecialFolder(fs3, WindowsFolder, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_GetSpecialFolder(fs3, TemporaryFolder+1, NULL);
    ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_GetSpecialFolder(fs3, TemporaryFolder+1, &folder);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);

    hr = IFileSystem3_GetSpecialFolder(fs3, WindowsFolder, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IFolder_get_Path(folder, &path);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetWindowsDirectoryW(pathW, ARRAY_SIZE(pathW));
    ok(!lstrcmpiW(pathW, path), "got %s, expected %s\n", wine_dbgstr_w(path), wine_dbgstr_w(pathW));
    SysFreeString(path);
    IFolder_Release(folder);

    hr = IFileSystem3_GetSpecialFolder(fs3, SystemFolder, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IFolder_get_Path(folder, &path);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    GetSystemDirectoryW(pathW, ARRAY_SIZE(pathW));
    ok(!lstrcmpiW(pathW, path), "got %s, expected %s\n", wine_dbgstr_w(path), wine_dbgstr_w(pathW));
    SysFreeString(path);
    IFolder_Release(folder);

    hr = IFileSystem3_GetSpecialFolder(fs3, TemporaryFolder, &folder);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    hr = IFolder_get_Path(folder, &path);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    ret = GetTempPathW(ARRAY_SIZE(pathW), pathW);
    if (ret && pathW[ret-1] == '\\')
        pathW[ret-1] = 0;

    ok(!lstrcmpiW(pathW, path), "got %s, expected %s\n", wine_dbgstr_w(path), wine_dbgstr_w(pathW));
    SysFreeString(path);
    IFolder_Release(folder);
}

static void test_MoveFile(void)
{
    ITextStream *stream;
    BSTR str, src, dst;
    HRESULT hr;

    str = SysAllocString(L"test.txt");
    hr = IFileSystem3_CreateTextFile(fs3, str, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    str = SysAllocString(L"test");
    hr = ITextStream_Write(stream, str);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    ITextStream_Release(stream);

    str = SysAllocString(L"test2.txt");
    hr = IFileSystem3_CreateTextFile(fs3, str, VARIANT_FALSE, VARIANT_FALSE, &stream);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
    ITextStream_Release(stream);

    src = SysAllocString(L"test.txt");
    dst = SysAllocString(L"test3.txt");
    hr = IFileSystem3_MoveFile(fs3, src, dst);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(src);
    SysFreeString(dst);

    str = SysAllocString(L"test.txt");
    hr = IFileSystem3_DeleteFile(fs3, str, VARIANT_TRUE);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    src = SysAllocString(L"test3.txt");
    dst = SysAllocString(L"test2.txt"); /* already exists */
    hr = IFileSystem3_MoveFile(fs3, src, dst);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Unexpected hr %#lx.\n", hr);
    SysFreeString(src);
    SysFreeString(dst);

    src = SysAllocString(L"nonexistent.txt");
    dst = SysAllocString(L"test4.txt");
    hr = IFileSystem3_MoveFile(fs3, src, dst);
    ok(hr == CTL_E_FILENOTFOUND, "Unexpected hr %#lx.\n", hr);
    SysFreeString(src);
    SysFreeString(dst);

    str = SysAllocString(L"test3.txt");
    hr = IFileSystem3_DeleteFile(fs3, str, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    str = SysAllocString(L"test2.txt");
    hr = IFileSystem3_DeleteFile(fs3, str, VARIANT_TRUE);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);

    str = SysAllocString(L"null.txt");
    hr = IFileSystem3_MoveFile(fs3, str, NULL);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFile(fs3, NULL, str);
    ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
    SysFreeString(str);
}

static void test_MoveFolder(void)
{
    WCHAR temp_path[MAX_PATH], cwd[MAX_PATH];
    BSTR src, dst, empty;
    HANDLE file;
    HRESULT hr;
    BOOL ret;

    GetCurrentDirectoryW(MAX_PATH, cwd);
    GetTempPathW(MAX_PATH, temp_path);
    SetCurrentDirectoryW(temp_path);
    src = SysAllocString(L"winetest_src");
    dst = SysAllocString(L"winetest_dst");

    ret = CreateDirectoryW(L"winetest_src", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == S_OK, "Got hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_PATHNOTFOUND, "Got hr %#lx.\n", hr);
    ret = CreateDirectoryW(L"winetest_src", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Got hr %#lx.\n", hr);
    ret = RemoveDirectoryW(L"winetest_dst");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_src");
    ok(ret, "Got error %lu.\n", GetLastError());

    empty = SysAllocString(L"");
    hr = IFileSystem3_MoveFolder(fs3, src, NULL);
    ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFolder(fs3, NULL, dst);
    ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFolder(fs3, src, empty);
    ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFolder(fs3, empty, dst);
    ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
    SysFreeString(empty);

    file = CreateFileW(L"winetest_src", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    ok(file != INVALID_HANDLE_VALUE, "Got error %lu.\n", GetLastError());
    CloseHandle(file);
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_PATHNOTFOUND, "Got hr %#lx.\n", hr);
    ret = DeleteFileW(L"winetest_src");
    ok(ret, "Got error %lu.\n", GetLastError());

    dst = SysAllocString(L"winetest_dst\\");

    ret = CreateDirectoryW(L"winetest_src", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_PATHNOTFOUND, "Got hr %#lx.\n", hr);
    ret = CreateDirectoryW(L"winetest_dst", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == S_OK, "Got hr %#lx.\n", hr);

    ret = RemoveDirectoryW(L"winetest_dst\\winetest_src");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_dst");
    ok(ret, "Got error %lu.\n", GetLastError());

    src = SysAllocString(L"winetest_src\\");
    dst = SysAllocString(L"winetest_dst");

    ret = CreateDirectoryW(L"winetest_src", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_ILLEGALFUNCTIONCALL, "Got hr %#lx.\n", hr);
    src = SysAllocString(L"winetest_src\\");
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_ILLEGALFUNCTIONCALL, "Got hr %#lx.\n", hr);

    ret = RemoveDirectoryW(L"winetest_src");
    ok(ret, "Got error %lu.\n", GetLastError());

    ret = CreateDirectoryW(L"winetest_src1", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = CreateDirectoryW(L"winetest_src2", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    file = CreateFileW(L"winetest_src3", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    ok(file != INVALID_HANDLE_VALUE, "Got error %lu.\n", GetLastError());
    CloseHandle(file);

    src = SysAllocString(L"winetest_src*");
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_PATHNOTFOUND, "Got hr %#lx.\n", hr);
    ret = CreateDirectoryW(L"winetest_dst", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == S_OK, "Got hr %#lx.\n", hr);
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_PATHNOTFOUND, "Got hr %#lx.\n", hr);
    ret = RemoveDirectoryW(L"winetest_dst/winetest_src1");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_dst/winetest_src2");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_dst");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = DeleteFileW(L"winetest_src3");
    ok(ret, "Got error %lu.\n", GetLastError());

    /* Fail to copy one file. */
    ret = CreateDirectoryW(L"winetest_src1", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = CreateDirectoryW(L"winetest_src2", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = CreateDirectoryW(L"winetest_src3", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = CreateDirectoryW(L"winetest_dst", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = CreateDirectoryW(L"winetest_dst\\winetest_src2", NULL);
    ok(ret, "Got error %lu.\n", GetLastError());
    hr = IFileSystem3_MoveFolder(fs3, src, dst);
    ok(hr == CTL_E_FILEALREADYEXISTS, "Got hr %#lx.\n", hr);
    ret = RemoveDirectoryW(L"winetest_dst/winetest_src1");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_dst/winetest_src2");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_dst");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_src2");
    ok(ret, "Got error %lu.\n", GetLastError());
    ret = RemoveDirectoryW(L"winetest_src3");
    ok(ret, "Got error %lu.\n", GetLastError());
}

static void test_DoOpenPipeStream(void)
{
    static const char testdata[] = "test";
    ITextStream *stream_read, *stream_write;
    SECURITY_ATTRIBUTES pipe_attr;
    HANDLE piperead, pipewrite;
    DWORD written;
    HRESULT hr;
    BSTR str;
    BOOL ret;

    pDoOpenPipeStream = (void *)GetProcAddress(GetModuleHandleA("scrrun.dll"), "DoOpenPipeStream");

    pipe_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
    pipe_attr.bInheritHandle = TRUE;
    pipe_attr.lpSecurityDescriptor = NULL;
    ret = CreatePipe(&piperead, &pipewrite, &pipe_attr, 0);
    ok(ret, "Failed to create pipes.\n");

    hr = pDoOpenPipeStream(piperead, ForReading, &stream_read);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
    if (SUCCEEDED(hr))
    {
        ok(!!stream_read, "Unexpected stream pointer.\n");

        ret = WriteFile(pipewrite, testdata, sizeof(testdata), &written, NULL);
        ok(ret, "Failed to write to the pipe.\n");
        ok(written == sizeof(testdata), "Write to anonymous pipe wrote %ld bytes.\n", written);

        hr = ITextStream_Read(stream_read, 4, &str);
        todo_wine
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (SUCCEEDED(hr))
        {
            ok(!wcscmp(str, L"test"), "Unexpected data read %s.\n", wine_dbgstr_w(str));
            SysFreeString(str);
        }

        ITextStream_Release(stream_read);
    }

    ret = CloseHandle(pipewrite);
    ok(ret, "Unexpected return value.\n");
    /* Stream takes ownership. */
    ret = CloseHandle(piperead);
    ok(!ret, "Unexpected return value.\n");

    /* Streams on both ends. */
    ret = CreatePipe(&piperead, &pipewrite, &pipe_attr, 0);
    ok(ret, "Failed to create pipes.\n");

    stream_read = NULL;
    hr = pDoOpenPipeStream(piperead, ForReading, &stream_read);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    stream_write = NULL;
    hr = pDoOpenPipeStream(pipewrite, ForWriting, &stream_write);
    ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

    if (SUCCEEDED(hr))
    {
        str = SysAllocString(L"data");
        hr = ITextStream_Write(stream_write, str);
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);

        hr = ITextStream_Write(stream_read, str);
        ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

        SysFreeString(str);

        hr = ITextStream_Read(stream_write, 1, &str);
        todo_wine
        ok(hr == CTL_E_BADFILEMODE, "Unexpected hr %#lx.\n", hr);

        hr = ITextStream_Read(stream_read, 4, &str);
        todo_wine
        ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
        if (SUCCEEDED(hr))
        {
            ok(!wcscmp(str, L"data"), "Unexpected data.\n");
            SysFreeString(str);
        }
    }

    if (stream_read)
        ITextStream_Release(stream_read);
    if (stream_write)
        ITextStream_Release(stream_write);
}

START_TEST(filesystem)
{
    HRESULT hr;

    CoInitialize(NULL);

    hr = CoCreateInstance(&CLSID_FileSystemObject, NULL, CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER,
            &IID_IFileSystem3, (void**)&fs3);
    if(FAILED(hr))
    {
        win_skip("Could not create FileSystem object, hr %#lx.\n", hr);
        return;
    }

    test_interfaces();
    test_createfolder();
    test_textstream();
    test_GetFileVersion();
    test_GetParentFolderName();
    test_GetFileName();
    test_GetBaseName();
    test_GetAbsolutePathName();
    test_GetFile();
    test_GetTempName();
    test_CopyFolder();
    test_BuildPath();
    test_GetFolder();
    test_FolderCollection();
    test_FileCollection();
    test_DriveCollection();
    test_CreateTextFile();
    test_FolderCreateTextFile();
    test_WriteLine();
    test_WriteBlankLines();
    test_ReadAll();
    test_Read();
    test_ReadLine();
    test_DriveExists();
    test_GetDriveName();
    test_GetDrive();
    test_SerialNumber();
    test_GetExtensionName();
    test_GetSpecialFolder();
    test_MoveFile();
    test_MoveFolder();
    test_DoOpenPipeStream();

    IFileSystem3_Release(fs3);

    CoUninitialize();
}
