FileSystemTests.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. #include <windows.h>
  2. #include <winstring.h> // For wil::unique_hstring
  3. #include <wil/common.h>
  4. #ifdef WIL_ENABLE_EXCEPTIONS
  5. #include <string>
  6. #endif
  7. // TODO: str_raw_ptr is not two-phase name lookup clean (https://github.com/Microsoft/wil/issues/8)
  8. namespace wil
  9. {
  10. PCWSTR str_raw_ptr(HSTRING);
  11. #ifdef WIL_ENABLE_EXCEPTIONS
  12. PCWSTR str_raw_ptr(const std::wstring&);
  13. #endif
  14. }
  15. #include <wil/filesystem.h>
  16. #ifdef WIL_ENABLE_EXCEPTIONS
  17. #include <wil/stl.h> // For std::wstring string_maker
  18. #endif
  19. #include "common.h"
  20. #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  21. bool DirectoryExists(_In_ PCWSTR path)
  22. {
  23. DWORD dwAttrib = GetFileAttributesW(path);
  24. return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
  25. (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
  26. }
  27. bool FileExists(_In_ PCWSTR path)
  28. {
  29. DWORD dwAttrib = GetFileAttributesW(path);
  30. return (dwAttrib != INVALID_FILE_ATTRIBUTES);
  31. }
  32. TEST_CASE("FileSystemTests::CreateDirectory", "[filesystem]")
  33. {
  34. wchar_t basePath[MAX_PATH];
  35. REQUIRE(GetTempPathW(ARRAYSIZE(basePath), basePath));
  36. REQUIRE_SUCCEEDED(PathCchAppend(basePath, ARRAYSIZE(basePath), L"FileSystemTests"));
  37. REQUIRE_FALSE(DirectoryExists(basePath));
  38. REQUIRE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath)));
  39. REQUIRE(DirectoryExists(basePath));
  40. auto scopeGuard = wil::scope_exit([&]
  41. {
  42. REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(basePath));
  43. });
  44. PCWSTR relativeTestPath = L"folder1\\folder2\\folder3\\folder4\\folder5\\folder6\\folder7\\folder8";
  45. wchar_t absoluteTestPath[MAX_PATH];
  46. REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath, ARRAYSIZE(absoluteTestPath), basePath));
  47. REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath, ARRAYSIZE(absoluteTestPath), relativeTestPath));
  48. REQUIRE_FALSE(DirectoryExists(absoluteTestPath));
  49. REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteTestPath));
  50. PCWSTR invalidCharsPath = L"Bad?Char|";
  51. wchar_t absoluteInvalidPath[MAX_PATH];
  52. REQUIRE_SUCCEEDED(StringCchCopyW(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), basePath));
  53. REQUIRE_SUCCEEDED(PathCchAppend(absoluteInvalidPath, ARRAYSIZE(absoluteInvalidPath), invalidCharsPath));
  54. REQUIRE_FALSE(DirectoryExists(absoluteInvalidPath));
  55. REQUIRE_FALSE(SUCCEEDED(wil::CreateDirectoryDeepNoThrow(absoluteInvalidPath)));
  56. PCWSTR testPath3 = L"folder1\\folder2\\folder3";
  57. wchar_t absoluteTestPath3[MAX_PATH];
  58. REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), basePath));
  59. REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath3, ARRAYSIZE(absoluteTestPath3), testPath3));
  60. REQUIRE(DirectoryExists(absoluteTestPath3));
  61. PCWSTR testPath4 = L"folder1\\folder2\\folder3\\folder4";
  62. wchar_t absoluteTestPath4[MAX_PATH];
  63. REQUIRE_SUCCEEDED(StringCchCopyW(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), basePath));
  64. REQUIRE_SUCCEEDED(PathCchAppend(absoluteTestPath4, ARRAYSIZE(absoluteTestPath4), testPath4));
  65. REQUIRE(DirectoryExists(absoluteTestPath4));
  66. REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(absoluteTestPath3, wil::RemoveDirectoryOptions::KeepRootDirectory));
  67. REQUIRE(DirectoryExists(absoluteTestPath3));
  68. REQUIRE_FALSE(DirectoryExists(absoluteTestPath4));
  69. }
  70. TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveDoesNotTraverseWithoutAHandle", "[filesystem]")
  71. {
  72. auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
  73. {
  74. wil::unique_hlocal_string path;
  75. REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
  76. return path;
  77. };
  78. wil::unique_cotaskmem_string tempPath;
  79. REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
  80. const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
  81. REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));
  82. auto scopeGuard = wil::scope_exit([&]
  83. {
  84. wil::RemoveDirectoryRecursiveNoThrow(basePath.get());
  85. });
  86. // Try to delete a directory whose handle is already taken.
  87. const auto folderToRecurse = CreateRelativePath(basePath.get(), L"folderToRecurse");
  88. REQUIRE(::CreateDirectoryW(folderToRecurse.get(), nullptr));
  89. const auto subfolderWithHandle = CreateRelativePath(folderToRecurse.get(), L"subfolderWithHandle");
  90. REQUIRE(::CreateDirectoryW(subfolderWithHandle.get(), nullptr));
  91. const auto childOfSubfolder = CreateRelativePath(subfolderWithHandle.get(), L"childOfSubfolder");
  92. REQUIRE(::CreateDirectoryW(childOfSubfolder.get(), nullptr));
  93. // Passing a 0 in share flags only allows metadata query on this file by other processes.
  94. // This should fail with a sharing violation error when any other action is taken.
  95. wil::unique_hfile subFolderHandle(::CreateFileW(subfolderWithHandle.get(), GENERIC_ALL,
  96. 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
  97. REQUIRE(subFolderHandle);
  98. REQUIRE(wil::RemoveDirectoryRecursiveNoThrow(folderToRecurse.get()) == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION));
  99. // Release the handle to allow cleanup.
  100. subFolderHandle.reset();
  101. }
  102. TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteReadOnlyFiles", "[filesystem]")
  103. {
  104. auto CreateRelativePath = [](PCWSTR root, PCWSTR name)
  105. {
  106. wil::unique_hlocal_string path;
  107. REQUIRE_SUCCEEDED(PathAllocCombine(root, name, PATHCCH_ALLOW_LONG_PATHS, &path));
  108. return path;
  109. };
  110. auto CreateReadOnlyFile = [](PCWSTR path)
  111. {
  112. wil::unique_hfile fileHandle(CreateFileW(path, 0,
  113. 0, nullptr, CREATE_ALWAYS,
  114. FILE_ATTRIBUTE_READONLY, nullptr));
  115. REQUIRE(fileHandle);
  116. };
  117. wil::unique_cotaskmem_string tempPath;
  118. REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(LR"(%TEMP%)", tempPath));
  119. const auto basePath = CreateRelativePath(tempPath.get(), L"FileSystemTests");
  120. REQUIRE_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(basePath.get()));
  121. auto scopeGuard = wil::scope_exit([&]
  122. {
  123. wil::RemoveDirectoryRecursiveNoThrow(basePath.get(), wil::RemoveDirectoryOptions::RemoveReadOnly);
  124. });
  125. // Create a reparse point and a target folder that shouldn't get deleted
  126. auto folderToDelete = CreateRelativePath(basePath.get(), L"folderToDelete");
  127. REQUIRE(::CreateDirectoryW(folderToDelete.get(), nullptr));
  128. auto topLevelReadOnly = CreateRelativePath(folderToDelete.get(), L"topLevelReadOnly.txt");
  129. CreateReadOnlyFile(topLevelReadOnly.get());
  130. auto subLevel = CreateRelativePath(folderToDelete.get(), L"subLevel");
  131. REQUIRE(::CreateDirectoryW(subLevel.get(), nullptr));
  132. auto subLevelReadOnly = CreateRelativePath(subLevel.get(), L"subLevelReadOnly.txt");
  133. CreateReadOnlyFile(subLevelReadOnly.get());
  134. // Delete will fail without the RemoveReadOnlyFlag
  135. REQUIRE_FAILED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get()));
  136. REQUIRE_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(folderToDelete.get(), wil::RemoveDirectoryOptions::RemoveReadOnly));
  137. // Verify all files have been deleted
  138. REQUIRE_FALSE(FileExists(subLevelReadOnly.get()));
  139. REQUIRE_FALSE(DirectoryExists(subLevel.get()));
  140. REQUIRE_FALSE(FileExists(topLevelReadOnly.get()));
  141. REQUIRE_FALSE(DirectoryExists(folderToDelete.get()));
  142. }
  143. #ifdef WIL_ENABLE_EXCEPTIONS
  144. // Learn about the Win32 API normalization here: https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/
  145. // This test verifies the ability of RemoveDirectoryRecursive to be able to delete files
  146. // that are in the non-normalized form.
  147. TEST_CASE("FileSystemTests::VerifyRemoveDirectoryRecursiveCanDeleteFoldersWithNonNormalizedNames", "[filesystem]")
  148. {
  149. // Extended length paths can access files with non-normalized names.
  150. // This function creates a path with that ability.
  151. auto CreatePathThatCanAccessNonNormalizedNames = [](PCWSTR root, PCWSTR name)
  152. {
  153. wil::unique_hlocal_string path;
  154. THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &path));
  155. REQUIRE(wil::is_extended_length_path(path.get()));
  156. return path;
  157. };
  158. // Regular paths are normalized in the Win32 APIs thus can't address files in the non-normalized form.
  159. // This function creates a regular path form but preserves the non-normalized parts of the input (for testing)
  160. auto CreateRegularPath = [](PCWSTR root, PCWSTR name)
  161. {
  162. wil::unique_hlocal_string path;
  163. THROW_IF_FAILED(PathAllocCombine(root, name, PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &path));
  164. REQUIRE_FALSE(wil::is_extended_length_path(path.get()));
  165. return path;
  166. };
  167. struct TestCases
  168. {
  169. PCWSTR CreateWithName;
  170. PCWSTR DeleteWithName;
  171. wil::unique_hlocal_string (*CreatePathFunction)(PCWSTR root, PCWSTR name);
  172. HRESULT ExpectedResult;
  173. };
  174. PCWSTR NormalizedName = L"Foo";
  175. PCWSTR NonNormalizedName = L"Foo."; // The dot at the end is what makes this non-normalized.
  176. const auto PathNotFoundError = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  177. TestCases tests[] =
  178. {
  179. { NormalizedName, NormalizedName, CreateRegularPath, S_OK },
  180. { NonNormalizedName, NormalizedName, CreateRegularPath, PathNotFoundError },
  181. { NormalizedName, NonNormalizedName, CreateRegularPath, S_OK },
  182. { NonNormalizedName, NonNormalizedName, CreateRegularPath, PathNotFoundError },
  183. { NormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
  184. { NonNormalizedName, NormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
  185. { NormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, PathNotFoundError },
  186. { NonNormalizedName, NonNormalizedName, CreatePathThatCanAccessNonNormalizedNames, S_OK },
  187. };
  188. auto folderRoot = wil::ExpandEnvironmentStringsW(LR"(%TEMP%)");
  189. REQUIRE_FALSE(wil::is_extended_length_path(folderRoot.get()));
  190. auto EnsureFolderWithNonCanonicalNameAndContentsExists = [&](const TestCases& test)
  191. {
  192. const auto enableNonNormalized = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
  193. wil::unique_hlocal_string targetFolder;
  194. // Create a folder for testing using the extended length form to enable
  195. // access to non-normalized forms of the path
  196. THROW_IF_FAILED(PathAllocCombine(folderRoot.get(), test.CreateWithName, enableNonNormalized, &targetFolder));
  197. // This ensures the folder is there and won't fail if it already exists (common when testing).
  198. wil::CreateDirectoryDeep(targetFolder.get());
  199. // Create a file in that folder with a non-normalized name (with the dot at the end).
  200. wil::unique_hlocal_string extendedFilePath;
  201. THROW_IF_FAILED(PathAllocCombine(targetFolder.get(), L"NonNormalized.", enableNonNormalized, &extendedFilePath));
  202. wil::unique_hfile fileHandle(CreateFileW(extendedFilePath.get(), FILE_WRITE_ATTRIBUTES,
  203. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
  204. CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
  205. THROW_LAST_ERROR_IF(!fileHandle);
  206. };
  207. for (auto const& test : tests)
  208. {
  209. // remove remnants from previous test that will cause failures
  210. wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NormalizedName).get());
  211. wil::RemoveDirectoryRecursiveNoThrow(CreatePathThatCanAccessNonNormalizedNames(folderRoot.get(), NonNormalizedName).get());
  212. EnsureFolderWithNonCanonicalNameAndContentsExists(test);
  213. auto deleteWithPath = test.CreatePathFunction(folderRoot.get(), test.DeleteWithName);
  214. const auto hr = wil::RemoveDirectoryRecursiveNoThrow(deleteWithPath.get());
  215. REQUIRE(test.ExpectedResult == hr);
  216. }
  217. }
  218. #endif
  219. // real paths to test
  220. const wchar_t c_variablePath[] = L"%systemdrive%\\Windows\\System32\\Windows.Storage.dll";
  221. const wchar_t c_expandedPath[] = L"c:\\Windows\\System32\\Windows.Storage.dll";
  222. // // paths that should not exist on the system
  223. const wchar_t c_missingVariable[] = L"%doesnotexist%\\doesnotexist.dll";
  224. const wchar_t c_missingPath[] = L"c:\\Windows\\System32\\doesnotexist.dll";
  225. const int c_stackBufferLimitTest = 5;
  226. #ifdef WIL_ENABLE_EXCEPTIONS
  227. TEST_CASE("FileSystemTests::VerifyGetCurrentDirectory", "[filesystem]")
  228. {
  229. auto pwd = wil::GetCurrentDirectoryW();
  230. REQUIRE(*pwd.get() != L'\0');
  231. }
  232. TEST_CASE("FileSystemTests::VerifyGetFullPathName", "[filesystem]")
  233. {
  234. PCWSTR fileName = L"ReadMe.txt";
  235. auto result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, nullptr);
  236. PCWSTR fileNameResult;
  237. result = wil::GetFullPathNameW<wil::unique_cotaskmem_string>(fileName, &fileNameResult);
  238. REQUIRE(wcscmp(fileName, fileNameResult) == 0);
  239. auto result2 = wil::GetFullPathNameW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileName, &fileNameResult);
  240. REQUIRE(wcscmp(fileName, fileNameResult) == 0);
  241. REQUIRE(wcscmp(result.get(), result2.get()) == 0);
  242. // The only negative test case I've found is a path > 32k.
  243. std::wstring big(1024 * 32, L'a');
  244. wil::unique_hstring output;
  245. auto hr = wil::GetFullPathNameW(big.c_str(), output, nullptr);
  246. REQUIRE(hr == HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE));
  247. }
  248. TEST_CASE("FileSystemTests::VerifyGetFinalPathNameByHandle", "[filesystem]")
  249. {
  250. wil::unique_hfile fileHandle(CreateFileW(c_expandedPath, FILE_READ_ATTRIBUTES,
  251. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  252. FILE_FLAG_BACKUP_SEMANTICS, nullptr));
  253. THROW_LAST_ERROR_IF(!fileHandle);
  254. auto name = wil::GetFinalPathNameByHandleW(fileHandle.get());
  255. auto name2 = wil::GetFinalPathNameByHandleW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(fileHandle.get());
  256. REQUIRE(wcscmp(name.get(), name2.get()) == 0);
  257. std::wstring path;
  258. auto hr = wil::GetFinalPathNameByHandleW(nullptr, path);
  259. REQUIRE(hr == E_HANDLE); // should be a usage error so be a fail fast.
  260. // A more legitimate case is a non file handler like a drive volume.
  261. wil::unique_hfile volumeHandle(CreateFileW(LR"(\\?\C:)", FILE_READ_ATTRIBUTES,
  262. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  263. FILE_FLAG_BACKUP_SEMANTICS, nullptr));
  264. THROW_LAST_ERROR_IF(!volumeHandle);
  265. const auto hr2 = wil::GetFinalPathNameByHandleW(volumeHandle.get(), path);
  266. REQUIRE(hr2 == HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION));
  267. }
  268. TEST_CASE("FileSystemTests::VerifyTrySearchPathW", "[filesystem]")
  269. {
  270. auto pathToTest = wil::TrySearchPathW(nullptr, c_expandedPath, nullptr);
  271. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  272. pathToTest = wil::TrySearchPathW(nullptr, c_missingPath, nullptr);
  273. REQUIRE(wil::string_get_not_null(pathToTest)[0] == L'\0');
  274. }
  275. #endif
  276. // Simple test to expand an environmental string
  277. TEST_CASE("FileSystemTests::VerifyExpandEnvironmentStringsW", "[filesystem]")
  278. {
  279. wil::unique_cotaskmem_string pathToTest;
  280. REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_variablePath, pathToTest));
  281. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  282. // This should effectively be a no-op
  283. REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_expandedPath, pathToTest));
  284. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  285. // Environment variable does not exist, but the call should still succeed
  286. REQUIRE_SUCCEEDED(wil::ExpandEnvironmentStringsW(c_missingVariable, pathToTest));
  287. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_missingVariable, -1, TRUE) == CSTR_EQUAL);
  288. }
  289. TEST_CASE("FileSystemTests::VerifySearchPathW", "[filesystem]")
  290. {
  291. wil::unique_cotaskmem_string pathToTest;
  292. REQUIRE_SUCCEEDED(wil::SearchPathW(nullptr, c_expandedPath, nullptr, pathToTest));
  293. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  294. REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::SearchPathW(nullptr, c_missingPath, nullptr, pathToTest));
  295. }
  296. TEST_CASE("FileSystemTests::VerifyExpandEnvAndSearchPath", "[filesystem]")
  297. {
  298. wil::unique_cotaskmem_string pathToTest;
  299. REQUIRE_SUCCEEDED(wil::ExpandEnvAndSearchPath(c_variablePath, pathToTest));
  300. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  301. // This test will exercise the case where AdaptFixedSizeToAllocatedResult will need to
  302. // reallocate the initial buffer to fit the final string.
  303. // This test is sufficient to test both wil::ExpandEnvironmentStringsW and wil::SeachPathW
  304. REQUIRE_SUCCEEDED((wil::ExpandEnvAndSearchPath<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(c_variablePath, pathToTest)));
  305. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, c_expandedPath, -1, TRUE) == CSTR_EQUAL);
  306. pathToTest.reset();
  307. REQUIRE(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == wil::ExpandEnvAndSearchPath(c_missingVariable, pathToTest));
  308. REQUIRE(pathToTest.get() == nullptr);
  309. }
  310. TEST_CASE("FileSystemTests::VerifyGetSystemDirectoryW", "[filesystem]")
  311. {
  312. wil::unique_cotaskmem_string pathToTest;
  313. REQUIRE_SUCCEEDED(wil::GetSystemDirectoryW(pathToTest));
  314. // allocate based on the string that wil::GetSystemDirectoryW returned
  315. size_t length = wcslen(pathToTest.get()) + 1;
  316. auto trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
  317. REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
  318. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
  319. // Force AdaptFixed* to realloc. Test stack boundary with small initial buffer limit, c_stackBufferLimitTest
  320. REQUIRE_SUCCEEDED((wil::GetSystemDirectoryW<wil::unique_cotaskmem_string, c_stackBufferLimitTest>(pathToTest)));
  321. // allocate based on the string that wil::GetSystemDirectoryW returned
  322. length = wcslen(pathToTest.get()) + 1;
  323. trueSystemDir = wil::make_cotaskmem_string_nothrow(nullptr, length);
  324. REQUIRE(GetSystemDirectoryW(trueSystemDir.get(), static_cast<UINT>(length)) > 0);
  325. REQUIRE(CompareStringOrdinal(pathToTest.get(), -1, trueSystemDir.get(), -1, TRUE) == CSTR_EQUAL);
  326. }
  327. struct has_operator_pcwstr
  328. {
  329. PCWSTR value;
  330. operator PCWSTR() const
  331. {
  332. return value;
  333. }
  334. };
  335. struct has_operator_pwstr
  336. {
  337. PWSTR value;
  338. operator PWSTR() const
  339. {
  340. return value;
  341. }
  342. };
  343. #ifdef WIL_ENABLE_EXCEPTIONS
  344. struct has_operator_wstr_ref
  345. {
  346. std::wstring value;
  347. operator const std::wstring&() const
  348. {
  349. return value;
  350. }
  351. };
  352. // E.g. mimics something like std::filesystem::path
  353. struct has_operator_wstr
  354. {
  355. std::wstring value;
  356. operator std::wstring() const
  357. {
  358. return value;
  359. }
  360. };
  361. #endif
  362. TEST_CASE("FileSystemTests::VerifyStrConcat", "[filesystem]")
  363. {
  364. SECTION("Concat with multiple strings")
  365. {
  366. PCWSTR test1 = L"Test1";
  367. #ifdef WIL_ENABLE_EXCEPTIONS
  368. std::wstring test2 = L"Test2";
  369. #else
  370. PCWSTR test2 = L"Test2";
  371. #endif
  372. WCHAR test3[6] = L"Test3";
  373. wil::unique_cotaskmem_string test4 = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"test4");
  374. wil::unique_hstring test5 = wil::make_unique_string_nothrow<wil::unique_hstring>(L"test5");
  375. has_operator_pcwstr test6{ L"Test6" };
  376. WCHAR test7Buffer[] = L"Test7";
  377. has_operator_pwstr test7{ test7Buffer };
  378. #ifdef WIL_ENABLE_EXCEPTIONS
  379. has_operator_wstr_ref test8{ L"Test8" };
  380. has_operator_wstr test9{ L"Test9" };
  381. #else
  382. PCWSTR test8 = L"Test8";
  383. PCWSTR test9 = L"Test9";
  384. #endif
  385. PCWSTR expectedStr = L"Test1Test2Test3Test4Test5Test6Test7Test8Test9";
  386. #ifdef WIL_ENABLE_EXCEPTIONS
  387. auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
  388. REQUIRE(CompareStringOrdinal(combinedString.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
  389. #endif
  390. wil::unique_cotaskmem_string combinedStringNT;
  391. REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1, test2, test3, test4, test5, test6, test7, test8, test9));
  392. REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
  393. auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1, test2, test3, test4, test5, test6, test7, test8, test9);
  394. REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
  395. }
  396. SECTION("Concat with single string")
  397. {
  398. PCWSTR test1 = L"Test1";
  399. #ifdef WIL_ENABLE_EXCEPTIONS
  400. auto combinedString = wil::str_concat<wil::unique_cotaskmem_string>(test1);
  401. REQUIRE(CompareStringOrdinal(combinedString.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
  402. #endif
  403. wil::unique_cotaskmem_string combinedStringNT;
  404. REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test1));
  405. REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
  406. auto combinedStringFF = wil::str_concat_failfast<wil::unique_cotaskmem_string>(test1);
  407. REQUIRE(CompareStringOrdinal(combinedStringFF.get(), -1, test1, -1, TRUE) == CSTR_EQUAL);
  408. }
  409. SECTION("Concat with existing string")
  410. {
  411. std::wstring test2 = L"Test2";
  412. WCHAR test3[6] = L"Test3";
  413. PCWSTR expectedStr = L"Test1Test2Test3";
  414. wil::unique_cotaskmem_string combinedStringNT = wil::make_unique_string_nothrow<wil::unique_cotaskmem_string>(L"Test1");
  415. REQUIRE_SUCCEEDED(wil::str_concat_nothrow(combinedStringNT, test2.c_str(), test3));
  416. REQUIRE(CompareStringOrdinal(combinedStringNT.get(), -1, expectedStr, -1, TRUE) == CSTR_EQUAL);
  417. }
  418. }
  419. TEST_CASE("FileSystemTests::VerifyStrPrintf", "[filesystem]")
  420. {
  421. #ifdef WIL_ENABLE_EXCEPTIONS
  422. auto formattedString = wil::str_printf<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
  423. REQUIRE(CompareStringOrdinal(formattedString.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
  424. #endif
  425. wil::unique_cotaskmem_string formattedStringNT;
  426. REQUIRE_SUCCEEDED(wil::str_printf_nothrow(formattedStringNT, L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28));
  427. REQUIRE(CompareStringOrdinal(formattedStringNT.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
  428. auto formattedStringFF = wil::str_printf_failfast<wil::unique_cotaskmem_string>(L"Test %s %c %d %4.2f", L"String", L'c', 42, 6.28);
  429. REQUIRE(CompareStringOrdinal(formattedStringFF.get(), -1, L"Test String c 42 6.28", -1, TRUE) == CSTR_EQUAL);
  430. }
  431. TEST_CASE("FileSystemTests::VerifyGetModuleFileNameW", "[filesystem]")
  432. {
  433. wil::unique_cotaskmem_string path;
  434. REQUIRE_SUCCEEDED(wil::GetModuleFileNameW(nullptr, path));
  435. auto len = wcslen(path.get());
  436. REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
  437. // Call again, but force multiple retries through a small initial buffer
  438. wil::unique_cotaskmem_string path2;
  439. REQUIRE_SUCCEEDED((wil::GetModuleFileNameW<wil::unique_cotaskmem_string, 4>(nullptr, path2)));
  440. REQUIRE(wcscmp(path.get(), path2.get()) == 0);
  441. REQUIRE_FAILED(wil::GetModuleFileNameW((HMODULE)INVALID_HANDLE_VALUE, path));
  442. #ifdef WIL_ENABLE_EXCEPTIONS
  443. auto wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
  444. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  445. #endif
  446. }
  447. #ifdef WIL_ENABLE_EXCEPTIONS
  448. wil::unique_cotaskmem_string NativeGetModuleFileNameWrap(HANDLE processHandle, HMODULE moduleHandle)
  449. {
  450. DWORD size = MAX_PATH * 4;
  451. auto path = wil::make_cotaskmem_string_nothrow(nullptr, size);
  452. DWORD copied = processHandle ?
  453. ::GetModuleFileNameExW(processHandle, moduleHandle, path.get(), size) :
  454. ::GetModuleFileNameW(moduleHandle, path.get(), size);
  455. REQUIRE(copied < size);
  456. return path;
  457. }
  458. #endif
  459. TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
  460. {
  461. wil::unique_cotaskmem_string path;
  462. REQUIRE_SUCCEEDED(wil::GetModuleFileNameExW(nullptr, nullptr, path));
  463. auto len = wcslen(path.get());
  464. REQUIRE(((len >= 4) && (wcscmp(path.get() + len - 4, L".exe") == 0)));
  465. // Call again, but force multiple retries through a small initial buffer
  466. wil::unique_cotaskmem_string path2;
  467. REQUIRE_SUCCEEDED((wil::GetModuleFileNameExW<wil::unique_cotaskmem_string, 4>(nullptr, nullptr, path2)));
  468. REQUIRE(wcscmp(path.get(), path2.get()) == 0);
  469. REQUIRE_FAILED(wil::GetModuleFileNameExW(nullptr, (HMODULE)INVALID_HANDLE_VALUE, path));
  470. #ifdef WIL_ENABLE_EXCEPTIONS
  471. auto wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, nullptr);
  472. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  473. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());
  474. wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), nullptr);
  475. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  476. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), nullptr).get());
  477. wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(nullptr);
  478. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  479. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, nullptr).get());
  480. HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");
  481. wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(nullptr, kernel32);
  482. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  483. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());
  484. wstringPath = wil::GetModuleFileNameExW<std::wstring, 15>(GetCurrentProcess(), kernel32);
  485. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  486. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(GetCurrentProcess(), kernel32).get());
  487. wstringPath = wil::GetModuleFileNameW<std::wstring, 15>(kernel32);
  488. REQUIRE(wstringPath.length() == ::wcslen(wstringPath.c_str()));
  489. REQUIRE(wstringPath == NativeGetModuleFileNameWrap(nullptr, kernel32).get());
  490. #endif
  491. }
  492. TEST_CASE("FileSystemTests::QueryFullProcessImageNameW and GetModuleFileNameW", "[filesystem]")
  493. {
  494. #ifdef WIL_ENABLE_EXCEPTIONS
  495. auto procName = wil::QueryFullProcessImageNameW<std::wstring>();
  496. auto moduleName = wil::GetModuleFileNameW<std::wstring>();
  497. REQUIRE(procName == moduleName);
  498. #endif
  499. }
  500. TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
  501. {
  502. WCHAR fullName[MAX_PATH * 4];
  503. DWORD fullNameSize = ARRAYSIZE(fullName);
  504. REQUIRE(::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, fullName, &fullNameSize));
  505. wil::unique_cotaskmem_string path;
  506. REQUIRE_SUCCEEDED(wil::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, path));
  507. REQUIRE(wcscmp(fullName, path.get()) == 0);
  508. wil::unique_cotaskmem nativePath;
  509. REQUIRE_SUCCEEDED((wil::QueryFullProcessImageNameW<wil::unique_cotaskmem_string, 15>(::GetCurrentProcess(), 0, path)));
  510. }
  511. #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)