common.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. #pragma once
  2. #include <windows.h>
  3. #include <PathCch.h>
  4. #include "catch.hpp"
  5. #include <wil/filesystem.h>
  6. #include <wil/result.h>
  7. #define REPORTS_ERROR(expr) witest::ReportsError(wistd::is_same<HRESULT, decltype(expr)>{}, [&]() { return expr; })
  8. #define REQUIRE_ERROR(expr) REQUIRE(REPORTS_ERROR(expr))
  9. #define REQUIRE_NOERROR(expr) REQUIRE_FALSE(REPORTS_ERROR(expr))
  10. #define CRASHES(expr) witest::DoesCodeCrash([&]() { return expr; })
  11. #define REQUIRE_CRASH(expr) REQUIRE(CRASHES(expr))
  12. #define REQUIRE_NOCRASH(expr) REQUIRE_FALSE(CRASHES(expr))
  13. // NOTE: SUCCEEDED/FAILED macros not used here since Catch2 can give us better diagnostics if it knows the HRESULT value
  14. #define REQUIRE_SUCCEEDED(expr) REQUIRE((HRESULT)(expr) >= 0)
  15. #define REQUIRE_FAILED(expr) REQUIRE((HRESULT)(expr) < 0)
  16. // MACRO double evaluation check.
  17. // The following macro illustrates a common problem with writing macros:
  18. // #define MY_MAX(a, b) (((a) > (b)) ? (a) : (b))
  19. // The issue is that whatever code is being used for both a and b is being executed twice.
  20. // This isn't harmful when thinking of constant numerics, but consider this example:
  21. // MY_MAX(4, InterlockedIncrement(&cCount))
  22. // This evaluates the (B) parameter twice and results in incrementing the counter twice.
  23. // We use MDEC in unit tests to verify that this kind of pattern is not present. A test
  24. // of this kind:
  25. // MY_MAX(MDEC(4), MDEC(InterlockedIncrement(&cCount))
  26. // will verify that the parameters are not evaluated more than once.
  27. #define MDEC(PARAM) (witest::details::MacroDoubleEvaluationCheck(__LINE__, #PARAM), PARAM)
  28. // There's some functionality that we need for testing that's not available for the app partition. Since those tests are
  29. // primarily compilation tests, declare what's needed here
  30. extern "C" {
  31. #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
  32. WINBASEAPI _Ret_maybenull_
  33. PVOID WINAPI AddVectoredExceptionHandler(_In_ ULONG First, _In_ PVECTORED_EXCEPTION_HANDLER Handler);
  34. WINBASEAPI
  35. ULONG WINAPI RemoveVectoredExceptionHandler(_In_ PVOID Handle);
  36. #endif
  37. }
  38. #pragma warning(push)
  39. #pragma warning(disable: 4702) // Unreachable code
  40. namespace witest
  41. {
  42. namespace details
  43. {
  44. inline void MacroDoubleEvaluationCheck(size_t uLine, _In_ const char* pszCode)
  45. {
  46. struct SEval
  47. {
  48. size_t uLine;
  49. const char* pszCode;
  50. };
  51. static SEval rgEval[15] = {};
  52. static size_t nOffset = 0;
  53. for (auto& eval : rgEval)
  54. {
  55. if ((eval.uLine == uLine) && (eval.pszCode != nullptr) && (0 == strcmp(pszCode, eval.pszCode)))
  56. {
  57. // This verification indicates that macro-double-evaluation check is firing for a particular usage of MDEC().
  58. FAIL("Expression '" << pszCode << "' double evaluated in macro on line " << uLine);
  59. }
  60. }
  61. rgEval[nOffset].uLine = uLine;
  62. rgEval[nOffset].pszCode = pszCode;
  63. nOffset = (nOffset + 1) % ARRAYSIZE(rgEval);
  64. }
  65. template <typename T>
  66. class AssignTemporaryValueCleanup
  67. {
  68. public:
  69. AssignTemporaryValueCleanup(_In_ AssignTemporaryValueCleanup const &) = delete;
  70. AssignTemporaryValueCleanup & operator=(_In_ AssignTemporaryValueCleanup const &) = delete;
  71. explicit AssignTemporaryValueCleanup(_Inout_ T *pVal, T val) WI_NOEXCEPT :
  72. m_pVal(pVal),
  73. m_valOld(*pVal)
  74. {
  75. *pVal = val;
  76. }
  77. AssignTemporaryValueCleanup(_Inout_ AssignTemporaryValueCleanup && other) WI_NOEXCEPT :
  78. m_pVal(other.m_pVal),
  79. m_valOld(other.m_valOld)
  80. {
  81. other.m_pVal = nullptr;
  82. }
  83. ~AssignTemporaryValueCleanup() WI_NOEXCEPT
  84. {
  85. operator()();
  86. }
  87. void operator()() WI_NOEXCEPT
  88. {
  89. if (m_pVal != nullptr)
  90. {
  91. *m_pVal = m_valOld;
  92. m_pVal = nullptr;
  93. }
  94. }
  95. void Dismiss() WI_NOEXCEPT
  96. {
  97. m_pVal = nullptr;
  98. }
  99. private:
  100. T *m_pVal;
  101. T m_valOld;
  102. };
  103. }
  104. // Use the following routine to allow for a variable to be swapped with another and automatically revert the
  105. // assignment at the end of the scope.
  106. // Example:
  107. // int nFoo = 10
  108. // {
  109. // auto revert = witest::AssignTemporaryValue(&nFoo, 12);
  110. // // nFoo will now be 12 within this scope...
  111. // }
  112. // // and nFoo is back to 10 within the outer scope
  113. template <typename T>
  114. inline witest::details::AssignTemporaryValueCleanup<T> AssignTemporaryValue(_Inout_ T *pVal, T val) WI_NOEXCEPT
  115. {
  116. return witest::details::AssignTemporaryValueCleanup<T>(pVal, val);
  117. }
  118. //! Global class which tracks objects that derive from @ref AllocatedObject.
  119. //! Use `witest::g_objectCount.Leaked()` to determine if an object deriving from `AllocatedObject` has been leaked.
  120. class GlobalCount
  121. {
  122. public:
  123. int m_count = 0;
  124. //! Returns `true` if there are any objects that derive from @ref AllocatedObject still in memory.
  125. bool Leaked() const
  126. {
  127. return (m_count != 0);
  128. }
  129. ~GlobalCount()
  130. {
  131. if (Leaked())
  132. {
  133. // NOTE: This runs when no test is active, but will still cause an assert failure to notify
  134. FAIL("GlobalCount is non-zero; there is a leak somewhere");
  135. }
  136. }
  137. };
  138. __declspec(selectany) GlobalCount g_objectCount;
  139. //! Derive an allocated test object from witest::AllocatedObject to ensure that those objects aren't leaked in the test.
  140. //! Note that you can call g_objectCount.Leaked() at any point to determine if a leak has already occurred (assuming that
  141. //! all objects should have been destroyed at that point.
  142. class AllocatedObject
  143. {
  144. public:
  145. AllocatedObject() { g_objectCount.m_count++; }
  146. ~AllocatedObject() { g_objectCount.m_count--; }
  147. };
  148. template <typename Lambda>
  149. bool DoesCodeThrow(Lambda&& callOp)
  150. {
  151. #ifdef WIL_ENABLE_EXCEPTIONS
  152. try
  153. #endif
  154. {
  155. callOp();
  156. }
  157. #ifdef WIL_ENABLE_EXCEPTIONS
  158. catch (...)
  159. {
  160. return true;
  161. }
  162. #endif
  163. return false;
  164. }
  165. [[noreturn]]
  166. inline void __stdcall TranslateFailFastException(PEXCEPTION_RECORD rec, PCONTEXT, DWORD)
  167. {
  168. // RaiseFailFastException cannot be continued or handled. By instead calling RaiseException, it allows us to
  169. // handle exceptions
  170. ::RaiseException(rec->ExceptionCode, rec->ExceptionFlags, rec->NumberParameters, rec->ExceptionInformation);
  171. #ifdef __clang__
  172. __builtin_unreachable();
  173. #endif
  174. }
  175. [[noreturn]]
  176. inline void __stdcall FakeFailfastWithContext(const wil::FailureInfo&) noexcept
  177. {
  178. ::RaiseException(STATUS_STACK_BUFFER_OVERRUN, 0, 0, nullptr);
  179. #ifdef __clang__
  180. __builtin_unreachable();
  181. #endif
  182. }
  183. constexpr DWORD msvc_exception_code = 0xE06D7363;
  184. // This is a MAJOR hack. Catch2 registers a vectored exception handler - which gets run before our handler below -
  185. // that interprets a set of exception codes as fatal. We don't want this behavior since we may be expecting such
  186. // crashes, so instead translate all exception codes to something not fatal
  187. inline LONG WINAPI TranslateExceptionCodeHandler(PEXCEPTION_POINTERS info)
  188. {
  189. if (info->ExceptionRecord->ExceptionCode != witest::msvc_exception_code)
  190. {
  191. info->ExceptionRecord->ExceptionCode = STATUS_STACK_BUFFER_OVERRUN;
  192. }
  193. return EXCEPTION_CONTINUE_SEARCH;
  194. }
  195. namespace details
  196. {
  197. inline bool DoesCodeCrash(wistd::function<void()>& callOp)
  198. {
  199. bool result = false;
  200. __try
  201. {
  202. callOp();
  203. }
  204. // Let C++ exceptions pass through
  205. __except ((::GetExceptionCode() != msvc_exception_code) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
  206. {
  207. result = true;
  208. }
  209. return result;
  210. }
  211. }
  212. inline bool DoesCodeCrash(wistd::function<void()> callOp)
  213. {
  214. // See above; we don't want to actually fail fast, so make sure we raise a different exception instead
  215. auto restoreHandler = AssignTemporaryValue(&wil::details::g_pfnRaiseFailFastException, TranslateFailFastException);
  216. auto restoreHandler2 = AssignTemporaryValue(&wil::details::g_pfnFailfastWithContextCallback, FakeFailfastWithContext);
  217. auto handler = AddVectoredExceptionHandler(1, TranslateExceptionCodeHandler);
  218. auto removeVectoredHandler = wil::scope_exit([&] { RemoveVectoredExceptionHandler(handler); });
  219. return details::DoesCodeCrash(callOp);
  220. }
  221. template <typename Lambda>
  222. bool ReportsError(wistd::false_type, Lambda&& callOp)
  223. {
  224. bool doesThrow = false;
  225. bool doesCrash = DoesCodeCrash([&]()
  226. {
  227. doesThrow = DoesCodeThrow(callOp);
  228. });
  229. return doesThrow || doesCrash;
  230. }
  231. template <typename Lambda>
  232. bool ReportsError(wistd::true_type, Lambda&& callOp)
  233. {
  234. return FAILED(callOp());
  235. }
  236. #ifdef WIL_ENABLE_EXCEPTIONS
  237. class TestFailureCache final :
  238. public wil::details::IFailureCallback
  239. {
  240. public:
  241. TestFailureCache() :
  242. m_callbackHolder(this)
  243. {
  244. }
  245. void clear()
  246. {
  247. m_failures.clear();
  248. }
  249. size_t size() const
  250. {
  251. return m_failures.size();
  252. }
  253. bool empty() const
  254. {
  255. return m_failures.empty();
  256. }
  257. const wil::FailureInfo& operator[](size_t pos) const
  258. {
  259. return m_failures.at(pos).GetFailureInfo();
  260. }
  261. // IFailureCallback
  262. bool NotifyFailure(wil::FailureInfo const & failure) WI_NOEXCEPT override
  263. {
  264. m_failures.emplace_back(failure);
  265. return false;
  266. }
  267. private:
  268. std::vector<wil::StoredFailureInfo> m_failures;
  269. wil::details::ThreadFailureCallbackHolder m_callbackHolder;
  270. };
  271. #endif
  272. inline HRESULT GetTempFileName(wchar_t (&result)[MAX_PATH])
  273. {
  274. wchar_t dir[MAX_PATH];
  275. RETURN_LAST_ERROR_IF(::GetTempPathW(MAX_PATH, dir) == 0);
  276. RETURN_LAST_ERROR_IF(::GetTempFileNameW(dir, L"wil", 0, result) == 0);
  277. return S_OK;
  278. }
  279. inline HRESULT CreateUniqueFolderPath(wchar_t (&buffer)[MAX_PATH], PCWSTR root = nullptr)
  280. {
  281. if (root)
  282. {
  283. RETURN_LAST_ERROR_IF(::GetTempFileNameW(root, L"wil", 0, buffer) == 0);
  284. }
  285. else
  286. {
  287. wchar_t tempPath[MAX_PATH];
  288. RETURN_LAST_ERROR_IF(::GetTempPathW(ARRAYSIZE(tempPath), tempPath) == 0);
  289. RETURN_LAST_ERROR_IF(::GetLongPathNameW(tempPath, tempPath, ARRAYSIZE(tempPath)) == 0);
  290. RETURN_LAST_ERROR_IF(::GetTempFileNameW(tempPath, L"wil", 0, buffer) == 0);
  291. }
  292. RETURN_IF_WIN32_BOOL_FALSE(DeleteFileW(buffer));
  293. PathCchRemoveExtension(buffer, ARRAYSIZE(buffer));
  294. return S_OK;
  295. }
  296. #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
  297. struct TestFolder
  298. {
  299. TestFolder()
  300. {
  301. if (SUCCEEDED(CreateUniqueFolderPath(m_path)) && SUCCEEDED(wil::CreateDirectoryDeepNoThrow(m_path)))
  302. {
  303. m_valid = true;
  304. }
  305. }
  306. TestFolder(PCWSTR path)
  307. {
  308. if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), path)) && SUCCEEDED(wil::CreateDirectoryDeepNoThrow(m_path)))
  309. {
  310. m_valid = true;
  311. }
  312. }
  313. TestFolder(const TestFolder&) = delete;
  314. TestFolder& operator=(const TestFolder&) = delete;
  315. TestFolder(TestFolder&& other)
  316. {
  317. if (other.m_valid)
  318. {
  319. m_valid = true;
  320. other.m_valid = false;
  321. wcscpy_s(m_path, other.m_path);
  322. }
  323. }
  324. ~TestFolder()
  325. {
  326. if (m_valid)
  327. {
  328. wil::RemoveDirectoryRecursiveNoThrow(m_path);
  329. }
  330. }
  331. operator bool() const
  332. {
  333. return m_valid;
  334. }
  335. operator PCWSTR() const
  336. {
  337. return m_path;
  338. }
  339. PCWSTR Path() const
  340. {
  341. return m_path;
  342. }
  343. private:
  344. bool m_valid = false;
  345. wchar_t m_path[MAX_PATH] = L"";
  346. };
  347. struct TestFile
  348. {
  349. TestFile(PCWSTR path)
  350. {
  351. if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), path)))
  352. {
  353. Create();
  354. }
  355. }
  356. TestFile(PCWSTR dirPath, PCWSTR fileName)
  357. {
  358. if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), dirPath)) && SUCCEEDED(PathCchAppend(m_path, ARRAYSIZE(m_path), fileName)))
  359. {
  360. Create();
  361. }
  362. }
  363. TestFile(const TestFile&) = delete;
  364. TestFile& operator=(const TestFile&) = delete;
  365. TestFile(TestFile&& other)
  366. {
  367. if (other.m_valid)
  368. {
  369. m_valid = true;
  370. m_deleteDir = other.m_deleteDir;
  371. other.m_valid = other.m_deleteDir = false;
  372. wcscpy_s(m_path, other.m_path);
  373. }
  374. }
  375. ~TestFile()
  376. {
  377. // Best effort on all of these
  378. if (m_valid)
  379. {
  380. ::DeleteFileW(m_path);
  381. }
  382. if (m_deleteDir)
  383. {
  384. size_t parentLength;
  385. if (wil::try_get_parent_path_range(m_path, &parentLength))
  386. {
  387. m_path[parentLength] = L'\0';
  388. ::RemoveDirectoryW(m_path);
  389. m_path[parentLength] = L'\\';
  390. }
  391. }
  392. }
  393. operator bool() const
  394. {
  395. return m_valid;
  396. }
  397. operator PCWSTR() const
  398. {
  399. return m_path;
  400. }
  401. PCWSTR Path() const
  402. {
  403. return m_path;
  404. }
  405. private:
  406. HRESULT Create()
  407. {
  408. WI_ASSERT(!m_valid && !m_deleteDir);
  409. wil::unique_hfile fileHandle(::CreateFileW(m_path,
  410. FILE_WRITE_ATTRIBUTES,
  411. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
  412. CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
  413. if (!fileHandle)
  414. {
  415. auto err = ::GetLastError();
  416. size_t parentLength;
  417. if ((err == ERROR_PATH_NOT_FOUND) && wil::try_get_parent_path_range(m_path, &parentLength))
  418. {
  419. m_path[parentLength] = L'\0';
  420. RETURN_IF_FAILED(wil::CreateDirectoryDeepNoThrow(m_path));
  421. m_deleteDir = true;
  422. m_path[parentLength] = L'\\';
  423. fileHandle.reset(::CreateFileW(m_path,
  424. FILE_WRITE_ATTRIBUTES,
  425. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
  426. CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
  427. RETURN_LAST_ERROR_IF(!fileHandle);
  428. }
  429. else
  430. {
  431. RETURN_WIN32(err);
  432. }
  433. }
  434. m_valid = true;
  435. return S_OK;
  436. }
  437. bool m_valid = false;
  438. bool m_deleteDir = false;
  439. wchar_t m_path[MAX_PATH] = L"";
  440. };
  441. #endif
  442. }
  443. #pragma warning(pop)