dolphin/Externals/WIL/tests/FileSystemTests.cpp

632 lines
27 KiB
C++

#include <windows.h>
#include <winstring.h> // For wil::unique_hstring
#include <wil/common.h>
#ifdef WIL_ENABLE_EXCEPTIONS
#include <string>
#endif
// TODO: str_raw_ptr is not two-phase name lookup clean (https://github.com/Microsoft/wil/issues/8)
namespace wil
{
PCWSTR str_raw_ptr(HSTRING);
#ifdef WIL_ENABLE_EXCEPTIONS
PCWSTR str_raw_ptr(const std::wstring&);
#endif
}
#include <wil/filesystem.h>
#ifdef WIL_ENABLE_EXCEPTIONS
#include <wil/stl.h> // For std::wstring string_maker
#endif
#include "common.h"
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
bool DirectoryExists(_In_ PCWSTR path)
{
DWORD dwAttrib = GetFileAttributesW(path);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
bool FileExists(_In_ PCWSTR path)
{
DWORD dwAttrib = GetFileAttributesW(path);
return (dwAttrib != INVALID_FILE_ATTRIBUTES);
}
TEST_CASE("FileSystemTests::CreateDirectory", "[filesystem]")
{
wchar_t basePath[MAX_PATH];
REQUIRE(GetTempPathW(ARRAYSIZE(basePath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(basePath, ARRAYSIZE(basePath), L"FileSystemTests"));
REQUIRE_FALSE(DirectoryExists(basePath));
REQUIRE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath)));
REQUIRE(DirectoryExists(basePath));
auto scopeGuard = wil::scope_exit([&]
{
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(basePath));
});
PCWSTR relativeTestPath = L"folder1\\folder2\\folder3\\folder4\\folder5\\folder6\\folder7\\folder8";
wchar_t absoluteTestPath[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath, ARRAYSIZE(absoluteTestPath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath, ARRAYSIZE(absoluteTestPath), relativeTestPath));
REQUIRE_FALSE(DirectoryExists(absoluteTestPath));
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteTestPath));
PCWSTR invalidCharsPath = L"Bad?Char|";
wchar_t absoluteInvalidPath[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), invalidCharsPath));
REQUIRE_FALSE(DirectoryExists(absoluteInvalidPath));
REQUIRE_FALSE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteInvalidPath)));
PCWSTR testPath3 = L"folder1\\folder2\\folder3";
wchar_t absoluteTestPath3[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), testPath3));
REQUIRE(DirectoryExists(absoluteTestPath3));
PCWSTR testPath4 = L"folder1\\folder2\\folder3\\folder4";
wchar_t absoluteTestPath4[MAX_PATH];
REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), basePath));
REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), testPath4));
REQUIRE(DirectoryExists(absoluteTestPath4));
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(absoluteTestPath3, wil::RemoveDirectoryOptions::KeepRootDirectory));
REQUIRE(DirectoryExists(absoluteTestPath3));
REQUIRE_FALSE(DirectoryExists(absoluteTestPath4));
}
TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveDoesNotTraverseWithoutAHandle", "[filesystem]")
{
auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
return path;
};
wil::unique_cotaskmem_string tempPath;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));
auto scopeGuard = wil::scope_exit([&]
{
wil::RemoveDirectoryRecursiveNoThrow(basePath.get());
});
// Try to delete a directory whose handle is already taken.
const auto folderToRecurse = CreateRelativePath(basePath.get(), L"folderToRecurse");
REQUIRE(::CreateDirectoryW(folderToRecurse.get(), nullptr));
const auto subfolderWithHandle = CreateRelativePath(folderToRecurse.get(), L"subfolderWithHandle");
REQUIRE(::CreateDirectoryW(subfolderWithHandle.get(), nullptr));
const auto childOfSubfolder = CreateRelativePath(subfolderWithHandle.get(), L"childOfSubfolder");
REQUIRE(::CreateDirectoryW(childOfSubfolder.get(), nullptr));
// Passing a 0 in share flags only allows metadata query on this file by other processes.
// This should fail with a sharing violation error when any other action is taken.
wil::unique_hfile subFolderHandle(::CreateFileW(subfolderWithHandle.get(), GENERIC_ALL,
0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
REQUIRE(subFolderHandle);
REQUIRE(wil::RemoveDirectoryRecursiveNoThrow(folderToRecurse.get()) == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION));
// Release the handle to allow cleanup.
subFolderHandle.reset();
}
TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteReadOnlyFiles", "[filesystem]")
{
auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
return path;
};
auto CreateReadOnlyFile = [](PCWSTR path)
{
wil::unique_hfile fileHandle(CreateFileW(path, 0,
0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_READONLY, nullptr));
REQUIRE(fileHandle);
};
wil::unique_cotaskmem_string tempPath;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));
auto scopeGuard = wil::scope_exit([&]
{
wil::RemoveDirectoryRecursiveNoThrow(basePath.get(), wil::RemoveDirectoryOptions::RemoveReadOnly);
});
// Create a reparse point and a target folder that shouldn't get deleted
auto folderToDelete = CreateRelativePath(basePath.get(), L"folderToDelete");
REQUIRE(::CreateDirectoryW(folderToDelete.get(), nullptr));
auto topLevelReadOnly = CreateRelativePath(folderToDelete.get(), L"topLevelReadOnly.txt");
CreateReadOnlyFile(topLevelReadOnly.get());
auto subLevel = CreateRelativePath(folderToDelete.get(), L"subLevel");
REQUIRE(::CreateDirectoryW(subLevel.get(), nullptr));
auto subLevelReadOnly = CreateRelativePath(subLevel.get(), L"subLevelReadOnly.txt");
CreateReadOnlyFile(subLevelReadOnly.get());
// Delete will fail without the RemoveReadOnlyFlag
REQUIRE_FAILED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get()));
REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get(), wil::RemoveDirectoryOptions::RemoveReadOnly));
// Verify all files have been deleted
REQUIRE_FALSE(FileExists(subLevelReadOnly.get()));
REQUIRE_FALSE(DirectoryExists(subLevel.get()));
REQUIRE_FALSE(FileExists(topLevelReadOnly.get()));
REQUIRE_FALSE(DirectoryExists(folderToDelete.get()));
}
#ifdef WIL_ENABLE_EXCEPTIONS
// Learn about the Win32 API normalization here: https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/
// This test verifies the ability of RemoveDirectoryRecursive to be able to delete files
// that are in the non-normalized form.
TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteFoldersWithNonNormalizedNames", "[filesystem]")
{
// Extended length paths can access files with non-normalized names.
// This function creates a path with that ability.
auto CreatePathThatCanAccessNonNormalizedNames = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &path));
REQUIRE(wil::is_extended_length_path(path.get()));
return path;
};
// Regular paths are normalized in the Win32 APIs thus can't address files in the non-normalized form.
// This function creates a regular path form but preserves the non-normalized parts of the input (for testing)
auto CreateRegularPath = [](PCWSTR root, PCWSTR name)
{
wil::unique_hlocal_string path;
THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &path));
REQUIRE_FALSE(wil::is_extended_length_path(path.get()));
return path;
};
struct TestCases
{
PCWSTR CreateWithName;
PCWSTR DeleteWithName;
wil::unique_hlocal_string (*CreatePathFunction)(PCWSTR root, PCWSTR name);
HRESULT ExpectedResult;
};
PCWSTR NormalizedName = L"Foo";
PCWSTR NonNormalizedName = L"Foo."; // The dot at the end is what makes this non-normalized.
const auto PathNotFoundError = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
TestCases tests[] =
{
{ NormalizedName, NormalizedName, CreateRegularPath, S_OK },
{ NonNormalizedName, NormalizedName, CreateRegularPath, PathNotFoundError },
{ NormalizedName, NonNormalizedName, CreateRegularPath, S_OK },
{ NonNormalizedName, NonNormalizedName, CreateRegularPath, PathNotFoundError },
{ NormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
{ NonNormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
{ NormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
{ NonNormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
};
auto folderRoot = wil::ExpandEnvironmentStringsW(LR"(%TEMP%)");
REQUIRE_FALSE(wil::is_extended_length_path(folderRoot.get()));
auto EnsureFolderWithNonCanonicalNameAndContentsExists = [&](const TestCases& test)
{
const auto enableNonNormalized = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
wil::unique_hlocal_string targetFolder;
// Create a folder for testing using the extended length form to enable
// access to non-normalized forms of the path
THROW_IF_FAILED(PathAllocCombine(folderRoot.get(), test.CreateWithName, enableNonNormalized, &targetFolder));
// This ensures the folder is there and won't fail if it already exists (common when testing).
wil::CreateDirectoryDeep(targetFolder.get());
// Create a file in that folder with a non-normalized name (with the dot at the end).
wil::unique_hlocal_string extendedFilePath;
THROW_IF_FAILED(PathAllocCombine(targetFolder.get(), L"NonNormalized.", enableNonNormalized, &extendedFilePath));
wil::unique_hfile fileHandle(CreateFileW(extendedFilePath.get(), FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
THROW_LAST_ERROR_IF(!fileHandle);
};
for (auto const& test : tests)
{
// remove remnants from previous test that will cause failures
wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NormalizedName).get());
wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NonNormalizedName).get());
EnsureFolderWithNonCanonicalNameAndContentsExists(test);
auto deleteWithPath = test.CreatePathFunction(folderRoot.get(), test.DeleteWithName);
const auto hr = wil::RemoveDirectoryRecursiveNoThrow(deleteWithPath.get());
REQUIRE(test.ExpectedResult == hr);
}
}
#endif
// real paths to test
const wchar_t c_variablePath[] = L"%systemdrive%\\Windows\\System32\\Windows.Storage.dll";
const wchar_t c_expandedPath[] = L"c:\\Windows\\System32\\Windows.Storage.dll";
// // paths that should not exist on the system
const wchar_t c_missingVariable[] = L"%doesnotexist%\\doesnotexist.dll";
const wchar_t c_missingPath[] = L"c:\\Windows\\System32\\doesnotexist.dll";
const int c_stackBufferLimitTest = 5;
#ifdef WIL_ENABLE_EXCEPTIONS
TEST_CASE("FileSystemTests::VerifyGetCurrentDirectory", "[filesystem]")
{
auto pwd = wil::GetCurrentDirectoryW();
REQUIRE(*pwd.get() != L'\0');
}
TEST_CASE("FileSystemTests::VerifyGetFullPathName", "[filesystem]")
{
PCWSTR fileName = L"ReadMe.txt";
auto result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, nullptr);
PCWSTR fileNameResult;
result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, &fileNameResult);
REQUIRE(wcscmp(fileName, fileNameResult) == 0);
auto result2 = wil::GetFullPathNameW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileName, &fileNameResult);
REQUIRE(wcscmp(fileName, fileNameResult) == 0);
REQUIRE(wcscmp(result.get(), result2.get()) == 0);
// The only negative test case I've found is a path > 32k.
std::wstring big(1024 * 32, L'a');
wil::unique_hstring output;
auto hr = wil::GetFullPathNameW(big.c_str(), output, nullptr);
REQUIRE(hr == HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE));
}
TEST_CASE("FileSystemTests::VerifyGetFinalPathNameByHandle", "[filesystem]")
{
wil::unique_hfile fileHandle(CreateFileW(c_expandedPath, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr));
THROW_LAST_ERROR_IF(!fileHandle);
auto name = wil::GetFinalPathNameByHandleW(fileHandle.get());
auto name2 = wil::GetFinalPathNameByHandleW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileHandle.get());
REQUIRE(wcscmp(name.get(), name2.get()) == 0);
std::wstring path;
auto hr = wil::GetFinalPathNameByHandleW(nullptr, path);
REQUIRE(hr == E_HANDLE); // should be a usage error so be a fail fast.
// A more legitimate case is a non file handler like a drive volume.
wil::unique_hfile volumeHandle(CreateFileW(LR"(\\?\C:)", FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr));
THROW_LAST_ERROR_IF(!volumeHandle);
const auto hr2 = wil::GetFinalPathNameByHandleW(volumeHandle.get(), path);
REQUIRE(hr2 == HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION));
}
TEST_CASE("FileSystemTests::VerifyTrySearchPathW", "[filesystem]")
{
auto pathToTest = wil::TrySearchPathW(nullptr, c_expandedPath, nullptr);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
pathToTest = wil::TrySearchPathW(nullptr, c_missingPath, nullptr);
REQUIRE(wil::string_get_not_null(pathToTest)[0] == L'\0');
}
#endif
// Simple test to expand an environmental string
TEST_CASE("FileSystemTests::VerifyExpandEnvironmentStringsW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_variablePath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// This should effectively be a no-op
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_expandedPath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// Environment variable does not exist, but the call should still succeed
REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_missingVariable, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_missingVariable, -1, TRUE) == CSTR_EQUAL);
}
TEST_CASE("FileSystemTests::VerifySearchPathW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::SearchPathW(nullptr, c_expandedPath, nullptr, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::SearchPathW(nullptr, c_missingPath, nullptr, pathToTest));
}
TEST_CASE("FileSystemTests::VerifyExpandEnvAndSearchPath", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::ExpandEnvAndSearchPath(c_variablePath, pathToTest));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
// This test will exercise the case where AdaptFixedSizeToAllocatedResult will need to
// reallocate the initial buffer to fit the final string.
// This test is sufficient to test both wil::ExpandEnvironmentStringsW and wil::SeachPathW
REQUIRE_SUCCEEDED((wil::ExpandEnvAndSearchPath<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(c_variablePath, pathToTest)));
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
pathToTest.reset();
REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::ExpandEnvAndSearchPath(c_missingVariable, pathToTest));
REQUIRE(pathToTest.get() == nullptr);
}
TEST_CASE("FileSystemTests::VerifyGetSystemDirectoryW", "[filesystem]")
{
wil::unique_cotaskmem_string pathToTest;
REQUIRE_SUCCEEDED(wil::GetSystemDirectoryW(pathToTest));
// allocate based on the string that wil::GetSystemDirectoryW returned
size_t length = wcslen(pathToTest.get()) + 1;
auto trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
// Force AdaptFixed* to realloc. Test stack boundary with small initial buffer limit, c_stackBufferLimitTest
REQUIRE_SUCCEEDED((wil::GetSystemDirectoryW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(pathToTest)));
// allocate based on the string that wil::GetSystemDirectoryW returned
length = wcslen(pathToTest.get()) + 1;
trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
}
struct has_operator_pcwstr
{
PCWSTR value;
operator PCWSTR() const
{
return value;
}
};
struct has_operator_pwstr
{
PWSTR value;
operator PWSTR() const
{
return value;
}
};
#ifdef WIL_ENABLE_EXCEPTIONS
struct has_operator_wstr_ref
{
std::wstring value;
operator const std::wstring&() const
{
return value;
}
};
// E.g. mimics something like std::filesystem::path
struct has_operator_wstr
{
std::wstring value;
operator std::wstring() const
{
return value;
}
};
#endif
TEST_CASE("FileSystemTests::VerifyStrConcat", "[filesystem]")
{
SECTION("Concat with multiple strings")
{
PCWSTR test1 = L"Test1";
#ifdef WIL_ENABLE_EXCEPTIONS
std::wstring test2 = L"Test2";
#else
PCWSTR test2 = L"Test2";
#endif
WCHAR test3[6] = L"Test3";
wil::unique_cotaskmem_string test4 = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"test4");
wil::unique_hstring test5 = wil::make_unique_string_nothrow<wil::unique_hstring>(L"test5");
has_operator_pcwstr test6{ L"Test6" };
WCHAR test7Buffer[] = L"Test7";
has_operator_pwstr test7{ test7Buffer };
#ifdef WIL_ENABLE_EXCEPTIONS
has_operator_wstr_ref test8{ L"Test8" };
has_operator_wstr test9{ L"Test9" };
#else
PCWSTR test8 = L"Test8";
PCWSTR test9 = L"Test9";
#endif
PCWSTR expectedStr = L"Test1Test2Test3Test4Test5Test6Test7Test8Test9";
#ifdef WIL_ENABLE_EXCEPTIONS
auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
REQUIRE(CompareStringOrdinal(combinedString.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string combinedStringNT;
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1, test2, test3, test4, test5, test6, test7, test8, test9));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
}
SECTION("Concat with single string")
{
PCWSTR test1 = L"Test1";
#ifdef WIL_ENABLE_EXCEPTIONS
auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1);
REQUIRE(CompareStringOrdinal(combinedString.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string combinedStringNT;
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1);
REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
}
SECTION("Concat with existing string")
{
std::wstring test2 = L"Test2";
WCHAR test3[6] = L"Test3";
PCWSTR expectedStr = L"Test1Test2Test3";
wil::unique_cotaskmem_string combinedStringNT = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"Test1");
REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test2.c_str(), test3));
REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
}
}
TEST_CASE("FileSystemTests::VerifyStrPrintf", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto formattedString = wil::str_printf<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
REQUIRE(CompareStringOrdinal(formattedString.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
#endif
wil::unique_cotaskmem_string formattedStringNT;
REQUIRE_SUCCEEDED(wil::str_printf_nothrow(formattedStringNT, L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28));
REQUIRE(CompareStringOrdinal(formattedStringNT.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
auto formattedStringFF = wil::str_printf_failfast<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
REQUIRE(CompareStringOrdinal(formattedStringFF.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
}
TEST_CASE("FileSystemTests::VerifyGetModuleFileNameW", "[filesystem]")
{
wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::GetModuleFileNameW(nullptr, path));
auto len = wcslen(path.get());
REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
// Call again, but force multiple retries through a small initial buffer
wil::unique_cotaskmem_string path2;
REQUIRE_SUCCEEDED((wil::GetModuleFileNameW<wil::unique_cotaskmem_string, 4>(nullptr, path2)));
REQUIRE(wcscmp(path.get(), path2.get()) == 0);
REQUIRE_FAILED(wil::GetModuleFileNameW((HMODULE)INVALID_HANDLE_VALUE, path));
#ifdef WIL_ENABLE_EXCEPTIONS
auto wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
#endif
}
#ifdef WIL_ENABLE_EXCEPTIONS
wil::unique_cotaskmem_string NativeGetModuleFileNameWrap(HANDLE processHandle, HMODULE moduleHandle)
{
DWORD size = MAX_PATH * 4;
auto path = wil::make_cotaskmem_string_nothrow(nullptr, size);
DWORD copied = processHandle ?
::GetModuleFileNameExW(processHandle, moduleHandle, path.get(), size) :
::GetModuleFileNameW(moduleHandle, path.get(), size);
REQUIRE(copied < size);
return path;
}
#endif
TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
{
wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::GetModuleFileNameExW(nullptr, nullptr, path));
auto len = wcslen(path.get());
REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
// Call again, but force multiple retries through a small initial buffer
wil::unique_cotaskmem_string path2;
REQUIRE_SUCCEEDED((wil::GetModuleFileNameExW<wil::unique_cotaskmem_string, 4>(nullptr, nullptr, path2)));
REQUIRE(wcscmp(path.get(), path2.get()) == 0);
REQUIRE_FAILED(wil::GetModuleFileNameExW(nullptr, (HMODULE)INVALID_HANDLE_VALUE, path));
#ifdef WIL_ENABLE_EXCEPTIONS
auto wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());
wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), nullptr).get());
wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());
HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");
wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());
wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), kernel32).get());
wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(kernel32);
REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());
#endif
}
TEST_CASE("FileSystemTests::QueryFullProcessImageNameW and GetModuleFileNameW", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto procName = wil::QueryFullProcessImageNameW<std::wstring>();
auto moduleName = wil::GetModuleFileNameW<std::wstring>();
REQUIRE(procName == moduleName);
#endif
}
TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
{
WCHAR fullName[MAX_PATH * 4];
DWORD fullNameSize = ARRAYSIZE(fullName);
REQUIRE(::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, fullName, &fullNameSize));
wil::unique_cotaskmem_string path;
REQUIRE_SUCCEEDED(wil::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, path));
REQUIRE(wcscmp(fullName, path.get()) == 0);
wil::unique_cotaskmem nativePath;
REQUIRE_SUCCEEDED((wil::QueryFullProcessImageNameW<wil::unique_cotaskmem_string, 15>(::GetCurrentProcess(), 0, path)));
}
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)