mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 14:19:46 -06:00
Add Windows Implementation Libraries
This commit is contained in:
510
Externals/WIL/tests/common.h
vendored
Normal file
510
Externals/WIL/tests/common.h
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <PathCch.h>
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <wil/filesystem.h>
|
||||
#include <wil/result.h>
|
||||
|
||||
#define REPORTS_ERROR(expr) witest::ReportsError(wistd::is_same<HRESULT, decltype(expr)>{}, [&]() { return expr; })
|
||||
#define REQUIRE_ERROR(expr) REQUIRE(REPORTS_ERROR(expr))
|
||||
#define REQUIRE_NOERROR(expr) REQUIRE_FALSE(REPORTS_ERROR(expr))
|
||||
|
||||
#define CRASHES(expr) witest::DoesCodeCrash([&]() { return expr; })
|
||||
#define REQUIRE_CRASH(expr) REQUIRE(CRASHES(expr))
|
||||
#define REQUIRE_NOCRASH(expr) REQUIRE_FALSE(CRASHES(expr))
|
||||
|
||||
// NOTE: SUCCEEDED/FAILED macros not used here since Catch2 can give us better diagnostics if it knows the HRESULT value
|
||||
#define REQUIRE_SUCCEEDED(expr) REQUIRE((HRESULT)(expr) >= 0)
|
||||
#define REQUIRE_FAILED(expr) REQUIRE((HRESULT)(expr) < 0)
|
||||
|
||||
// MACRO double evaluation check.
|
||||
// The following macro illustrates a common problem with writing macros:
|
||||
// #define MY_MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
// The issue is that whatever code is being used for both a and b is being executed twice.
|
||||
// This isn't harmful when thinking of constant numerics, but consider this example:
|
||||
// MY_MAX(4, InterlockedIncrement(&cCount))
|
||||
// This evaluates the (B) parameter twice and results in incrementing the counter twice.
|
||||
// We use MDEC in unit tests to verify that this kind of pattern is not present. A test
|
||||
// of this kind:
|
||||
// MY_MAX(MDEC(4), MDEC(InterlockedIncrement(&cCount))
|
||||
// will verify that the parameters are not evaluated more than once.
|
||||
#define MDEC(PARAM) (witest::details::MacroDoubleEvaluationCheck(__LINE__, #PARAM), PARAM)
|
||||
|
||||
// There's some functionality that we need for testing that's not available for the app partition. Since those tests are
|
||||
// primarily compilation tests, declare what's needed here
|
||||
extern "C" {
|
||||
|
||||
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
|
||||
WINBASEAPI _Ret_maybenull_
|
||||
PVOID WINAPI AddVectoredExceptionHandler(_In_ ULONG First, _In_ PVECTORED_EXCEPTION_HANDLER Handler);
|
||||
|
||||
WINBASEAPI
|
||||
ULONG WINAPI RemoveVectoredExceptionHandler(_In_ PVOID Handle);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4702) // Unreachable code
|
||||
|
||||
namespace witest
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
inline void MacroDoubleEvaluationCheck(size_t uLine, _In_ const char* pszCode)
|
||||
{
|
||||
struct SEval
|
||||
{
|
||||
size_t uLine;
|
||||
const char* pszCode;
|
||||
};
|
||||
|
||||
static SEval rgEval[15] = {};
|
||||
static size_t nOffset = 0;
|
||||
|
||||
for (auto& eval : rgEval)
|
||||
{
|
||||
if ((eval.uLine == uLine) && (eval.pszCode != nullptr) && (0 == strcmp(pszCode, eval.pszCode)))
|
||||
{
|
||||
// This verification indicates that macro-double-evaluation check is firing for a particular usage of MDEC().
|
||||
FAIL("Expression '" << pszCode << "' double evaluated in macro on line " << uLine);
|
||||
}
|
||||
}
|
||||
|
||||
rgEval[nOffset].uLine = uLine;
|
||||
rgEval[nOffset].pszCode = pszCode;
|
||||
nOffset = (nOffset + 1) % ARRAYSIZE(rgEval);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class AssignTemporaryValueCleanup
|
||||
{
|
||||
public:
|
||||
AssignTemporaryValueCleanup(_In_ AssignTemporaryValueCleanup const &) = delete;
|
||||
AssignTemporaryValueCleanup & operator=(_In_ AssignTemporaryValueCleanup const &) = delete;
|
||||
|
||||
explicit AssignTemporaryValueCleanup(_Inout_ T *pVal, T val) WI_NOEXCEPT :
|
||||
m_pVal(pVal),
|
||||
m_valOld(*pVal)
|
||||
{
|
||||
*pVal = val;
|
||||
}
|
||||
|
||||
AssignTemporaryValueCleanup(_Inout_ AssignTemporaryValueCleanup && other) WI_NOEXCEPT :
|
||||
m_pVal(other.m_pVal),
|
||||
m_valOld(other.m_valOld)
|
||||
{
|
||||
other.m_pVal = nullptr;
|
||||
}
|
||||
|
||||
~AssignTemporaryValueCleanup() WI_NOEXCEPT
|
||||
{
|
||||
operator()();
|
||||
}
|
||||
|
||||
void operator()() WI_NOEXCEPT
|
||||
{
|
||||
if (m_pVal != nullptr)
|
||||
{
|
||||
*m_pVal = m_valOld;
|
||||
m_pVal = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Dismiss() WI_NOEXCEPT
|
||||
{
|
||||
m_pVal = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
T *m_pVal;
|
||||
T m_valOld;
|
||||
};
|
||||
}
|
||||
|
||||
// Use the following routine to allow for a variable to be swapped with another and automatically revert the
|
||||
// assignment at the end of the scope.
|
||||
// Example:
|
||||
// int nFoo = 10
|
||||
// {
|
||||
// auto revert = witest::AssignTemporaryValue(&nFoo, 12);
|
||||
// // nFoo will now be 12 within this scope...
|
||||
// }
|
||||
// // and nFoo is back to 10 within the outer scope
|
||||
template <typename T>
|
||||
inline witest::details::AssignTemporaryValueCleanup<T> AssignTemporaryValue(_Inout_ T *pVal, T val) WI_NOEXCEPT
|
||||
{
|
||||
return witest::details::AssignTemporaryValueCleanup<T>(pVal, val);
|
||||
}
|
||||
|
||||
//! Global class which tracks objects that derive from @ref AllocatedObject.
|
||||
//! Use `witest::g_objectCount.Leaked()` to determine if an object deriving from `AllocatedObject` has been leaked.
|
||||
class GlobalCount
|
||||
{
|
||||
public:
|
||||
int m_count = 0;
|
||||
|
||||
//! Returns `true` if there are any objects that derive from @ref AllocatedObject still in memory.
|
||||
bool Leaked() const
|
||||
{
|
||||
return (m_count != 0);
|
||||
}
|
||||
|
||||
~GlobalCount()
|
||||
{
|
||||
if (Leaked())
|
||||
{
|
||||
// NOTE: This runs when no test is active, but will still cause an assert failure to notify
|
||||
FAIL("GlobalCount is non-zero; there is a leak somewhere");
|
||||
}
|
||||
}
|
||||
};
|
||||
__declspec(selectany) GlobalCount g_objectCount;
|
||||
|
||||
//! Derive an allocated test object from witest::AllocatedObject to ensure that those objects aren't leaked in the test.
|
||||
//! Note that you can call g_objectCount.Leaked() at any point to determine if a leak has already occurred (assuming that
|
||||
//! all objects should have been destroyed at that point.
|
||||
class AllocatedObject
|
||||
{
|
||||
public:
|
||||
AllocatedObject() { g_objectCount.m_count++; }
|
||||
~AllocatedObject() { g_objectCount.m_count--; }
|
||||
};
|
||||
|
||||
template <typename Lambda>
|
||||
bool DoesCodeThrow(Lambda&& callOp)
|
||||
{
|
||||
#ifdef WIL_ENABLE_EXCEPTIONS
|
||||
try
|
||||
#endif
|
||||
{
|
||||
callOp();
|
||||
}
|
||||
#ifdef WIL_ENABLE_EXCEPTIONS
|
||||
catch (...)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
inline void __stdcall TranslateFailFastException(PEXCEPTION_RECORD rec, PCONTEXT, DWORD)
|
||||
{
|
||||
// RaiseFailFastException cannot be continued or handled. By instead calling RaiseException, it allows us to
|
||||
// handle exceptions
|
||||
::RaiseException(rec->ExceptionCode, rec->ExceptionFlags, rec->NumberParameters, rec->ExceptionInformation);
|
||||
#ifdef __clang__
|
||||
__builtin_unreachable();
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr DWORD msvc_exception_code = 0xE06D7363;
|
||||
|
||||
// This is a MAJOR hack. Catch2 registers a vectored exception handler - which gets run before our handler below -
|
||||
// that interprets a set of exception codes as fatal. We don't want this behavior since we may be expecting such
|
||||
// crashes, so instead translate all exception codes to something not fatal
|
||||
inline LONG WINAPI TranslateExceptionCodeHandler(PEXCEPTION_POINTERS info)
|
||||
{
|
||||
if (info->ExceptionRecord->ExceptionCode != witest::msvc_exception_code)
|
||||
{
|
||||
info->ExceptionRecord->ExceptionCode = STATUS_STACK_BUFFER_OVERRUN;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
namespace details
|
||||
{
|
||||
inline bool DoesCodeCrash(wistd::function<void()>& callOp)
|
||||
{
|
||||
bool result = false;
|
||||
__try
|
||||
{
|
||||
callOp();
|
||||
}
|
||||
// Let C++ exceptions pass through
|
||||
__except ((::GetExceptionCode() != msvc_exception_code) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool DoesCodeCrash(wistd::function<void()> callOp)
|
||||
{
|
||||
// See above; we don't want to actually fail fast, so make sure we raise a different exception instead
|
||||
auto restoreHandler = AssignTemporaryValue(&wil::details::g_pfnRaiseFailFastException, TranslateFailFastException);
|
||||
|
||||
auto handler = AddVectoredExceptionHandler(1, TranslateExceptionCodeHandler);
|
||||
auto removeVectoredHandler = wil::scope_exit([&] { RemoveVectoredExceptionHandler(handler); });
|
||||
|
||||
return details::DoesCodeCrash(callOp);
|
||||
}
|
||||
|
||||
template <typename Lambda>
|
||||
bool ReportsError(wistd::false_type, Lambda&& callOp)
|
||||
{
|
||||
bool doesThrow = false;
|
||||
bool doesCrash = DoesCodeCrash([&]()
|
||||
{
|
||||
doesThrow = DoesCodeThrow(callOp);
|
||||
});
|
||||
|
||||
return doesThrow || doesCrash;
|
||||
}
|
||||
|
||||
template <typename Lambda>
|
||||
bool ReportsError(wistd::true_type, Lambda&& callOp)
|
||||
{
|
||||
return FAILED(callOp());
|
||||
}
|
||||
|
||||
#ifdef WIL_ENABLE_EXCEPTIONS
|
||||
class TestFailureCache final :
|
||||
public wil::details::IFailureCallback
|
||||
{
|
||||
public:
|
||||
TestFailureCache() :
|
||||
m_callbackHolder(this)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_failures.clear();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_failures.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return m_failures.empty();
|
||||
}
|
||||
|
||||
const wil::FailureInfo& operator[](size_t pos) const
|
||||
{
|
||||
return m_failures.at(pos).GetFailureInfo();
|
||||
}
|
||||
|
||||
// IFailureCallback
|
||||
bool NotifyFailure(wil::FailureInfo const & failure) WI_NOEXCEPT override
|
||||
{
|
||||
m_failures.emplace_back(failure);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<wil::StoredFailureInfo> m_failures;
|
||||
wil::details::ThreadFailureCallbackHolder m_callbackHolder;
|
||||
};
|
||||
#endif
|
||||
|
||||
inline HRESULT GetTempFileName(wchar_t (&result)[MAX_PATH])
|
||||
{
|
||||
wchar_t dir[MAX_PATH];
|
||||
RETURN_LAST_ERROR_IF(::GetTempPathW(MAX_PATH, dir) == 0);
|
||||
RETURN_LAST_ERROR_IF(::GetTempFileNameW(dir, L"wil", 0, result) == 0);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
inline HRESULT CreateUniqueFolderPath(wchar_t (&buffer)[MAX_PATH], PCWSTR root = nullptr)
|
||||
{
|
||||
if (root)
|
||||
{
|
||||
RETURN_LAST_ERROR_IF(::GetTempFileNameW(root, L"wil", 0, buffer) == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
wchar_t tempPath[MAX_PATH];
|
||||
RETURN_LAST_ERROR_IF(::GetTempPathW(ARRAYSIZE(tempPath), tempPath) == 0);
|
||||
RETURN_LAST_ERROR_IF(::GetLongPathNameW(tempPath, tempPath, ARRAYSIZE(tempPath)) == 0);
|
||||
RETURN_LAST_ERROR_IF(::GetTempFileNameW(tempPath, L"wil", 0, buffer) == 0);
|
||||
}
|
||||
RETURN_IF_WIN32_BOOL_FALSE(DeleteFileW(buffer));
|
||||
PathCchRemoveExtension(buffer, ARRAYSIZE(buffer));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
|
||||
struct TestFolder
|
||||
{
|
||||
TestFolder()
|
||||
{
|
||||
if (SUCCEEDED(CreateUniqueFolderPath(m_path)) && SUCCEEDED(wil::CreateDirectoryDeepNoThrow(m_path)))
|
||||
{
|
||||
m_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
TestFolder(PCWSTR path)
|
||||
{
|
||||
if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), path)) && SUCCEEDED(wil::CreateDirectoryDeepNoThrow(m_path)))
|
||||
{
|
||||
m_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
TestFolder(const TestFolder&) = delete;
|
||||
TestFolder& operator=(const TestFolder&) = delete;
|
||||
|
||||
TestFolder(TestFolder&& other)
|
||||
{
|
||||
if (other.m_valid)
|
||||
{
|
||||
m_valid = true;
|
||||
other.m_valid = false;
|
||||
wcscpy_s(m_path, other.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
~TestFolder()
|
||||
{
|
||||
if (m_valid)
|
||||
{
|
||||
wil::RemoveDirectoryRecursiveNoThrow(m_path);
|
||||
}
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
operator PCWSTR() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
PCWSTR Path() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool m_valid = false;
|
||||
wchar_t m_path[MAX_PATH] = L"";
|
||||
};
|
||||
|
||||
struct TestFile
|
||||
{
|
||||
TestFile(PCWSTR path)
|
||||
{
|
||||
if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), path)))
|
||||
{
|
||||
Create();
|
||||
}
|
||||
}
|
||||
|
||||
TestFile(PCWSTR dirPath, PCWSTR fileName)
|
||||
{
|
||||
if (SUCCEEDED(StringCchCopyW(m_path, ARRAYSIZE(m_path), dirPath)) && SUCCEEDED(PathCchAppend(m_path, ARRAYSIZE(m_path), fileName)))
|
||||
{
|
||||
Create();
|
||||
}
|
||||
}
|
||||
|
||||
TestFile(const TestFile&) = delete;
|
||||
TestFile& operator=(const TestFile&) = delete;
|
||||
|
||||
TestFile(TestFile&& other)
|
||||
{
|
||||
if (other.m_valid)
|
||||
{
|
||||
m_valid = true;
|
||||
m_deleteDir = other.m_deleteDir;
|
||||
other.m_valid = other.m_deleteDir = false;
|
||||
wcscpy_s(m_path, other.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
~TestFile()
|
||||
{
|
||||
// Best effort on all of these
|
||||
if (m_valid)
|
||||
{
|
||||
::DeleteFileW(m_path);
|
||||
}
|
||||
if (m_deleteDir)
|
||||
{
|
||||
size_t parentLength;
|
||||
if (wil::try_get_parent_path_range(m_path, &parentLength))
|
||||
{
|
||||
m_path[parentLength] = L'\0';
|
||||
::RemoveDirectoryW(m_path);
|
||||
m_path[parentLength] = L'\\';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
operator PCWSTR() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
PCWSTR Path() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
HRESULT Create()
|
||||
{
|
||||
WI_ASSERT(!m_valid && !m_deleteDir);
|
||||
wil::unique_hfile fileHandle(::CreateFileW(m_path,
|
||||
FILE_WRITE_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
|
||||
if (!fileHandle)
|
||||
{
|
||||
auto err = ::GetLastError();
|
||||
size_t parentLength;
|
||||
if ((err == ERROR_PATH_NOT_FOUND) && wil::try_get_parent_path_range(m_path, &parentLength))
|
||||
{
|
||||
m_path[parentLength] = L'\0';
|
||||
RETURN_IF_FAILED(wil::CreateDirectoryDeepNoThrow(m_path));
|
||||
m_deleteDir = true;
|
||||
|
||||
m_path[parentLength] = L'\\';
|
||||
fileHandle.reset(::CreateFileW(m_path,
|
||||
FILE_WRITE_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
|
||||
RETURN_LAST_ERROR_IF(!fileHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_WIN32(err);
|
||||
}
|
||||
}
|
||||
|
||||
m_valid = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool m_valid = false;
|
||||
bool m_deleteDir = false;
|
||||
wchar_t m_path[MAX_PATH] = L"";
|
||||
};
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
Reference in New Issue
Block a user