diff options
Diffstat (limited to 'libs/usvfs/test/test_utils')
| -rw-r--r-- | libs/usvfs/test/test_utils/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | libs/usvfs/test/test_utils/test_helpers.cpp | 248 | ||||
| -rw-r--r-- | libs/usvfs/test/test_utils/test_helpers.h | 165 |
3 files changed, 424 insertions, 0 deletions
diff --git a/libs/usvfs/test/test_utils/CMakeLists.txt b/libs/usvfs/test/test_utils/CMakeLists.txt new file mode 100644 index 0000000..88c75d6 --- /dev/null +++ b/libs/usvfs/test/test_utils/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.16) + +find_package(GTest CONFIG REQUIRED) + +add_library(test_utils STATIC + test_helpers.cpp + test_helpers.h +) +set_target_properties(test_utils PROPERTIES FOLDER tests) +target_link_libraries(test_utils PUBLIC shared) +target_include_directories(test_utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/libs/usvfs/test/test_utils/test_helpers.cpp b/libs/usvfs/test/test_utils/test_helpers.cpp new file mode 100644 index 0000000..2daf3bb --- /dev/null +++ b/libs/usvfs/test/test_utils/test_helpers.cpp @@ -0,0 +1,248 @@ +#pragma once + +#include <format> + +#include "test_helpers.h" +#include "winapi.h" + +namespace test +{ + +std::string FuncFailed::msg(std::string_view func, const char* arg1, + const unsigned long* res, const char* what) +{ + std::string buffer; + buffer.reserve(128); + + std::format_to(std::back_inserter(buffer), "{}() {}", func, what ? what : "failed"); + const char* sep = " : "; + if (arg1) { + std::format_to(std::back_inserter(buffer), "{}{}", sep, arg1); + sep = ", "; + } + if (res) { + std::format_to(std::back_inserter(buffer), "{}result = {} ({:#x})", sep, *res, + *res); + } + return buffer; +} + +ScopedFILE ScopedFILE::open(const std::filesystem::path& filepath, + std::wstring_view mode, errno_t& err) +{ + FILE* fp = nullptr; + err = _wfopen_s(&fp, filepath.c_str(), mode.data()); + if (err || !fp) { + return ScopedFILE(); + } + return ScopedFILE(fp); +} + +ScopedFILE ScopedFILE::open(const std::filesystem::path& filepath, + std::wstring_view mode) +{ + errno_t err; + auto file = open(filepath, mode, err); + if (err || !file) { + throw_testWinFuncFailed("_wfopen_s", filepath.string().c_str(), err); + } + return file; +} + +path path_of_test_bin(const path& relative) +{ + path base(winapi::wide::getModuleFileName(nullptr)); + return relative.empty() ? base.parent_path() : base.parent_path() / relative; +} + +path path_of_test_temp(const path& relative) +{ + return path_of_test_bin().parent_path() / "temp" / relative; +} + +path path_of_test_fixtures(const path& relative) +{ + return path_of_test_bin().parent_path() / "fixtures" / relative; +} + +path path_of_usvfs_lib(const path& relative) +{ + return path_of_test_bin().parent_path().parent_path() / "lib" / relative; +} + +std::string platform_dependant_executable(const char* name, const char* ext, + const char* platform) +{ + std::string res = name; + res += "_"; + if (platform) + res += platform; + else +#if _WIN64 + res += "x64"; +#else + res += "x86"; +#endif + res += "."; + res += ext; + return res; +} + +path path_as_relative(const path& base, const path& full_path) +{ + auto rel_begin = full_path.begin(); + auto base_iter = base.begin(); + while (rel_begin != full_path.end() && base_iter != base.end() && + *rel_begin == *base_iter) { + ++rel_begin; + ++base_iter; + } + + if (base_iter != base.end()) // full_path is not a sub-folder of base + return full_path; + + if (rel_begin == full_path.end()) // full_path == base + return path(L"."); + + // full_path is a sub-folder of base so take only relative path + path result; + for (; rel_begin != full_path.end(); ++rel_begin) + result /= *rel_begin; + return result; +} + +std::vector<char> read_small_file(const path& file, bool binary) +{ + using namespace std; + + const auto f = ScopedFILE::open(file, binary ? L"rb" : L"rt"); + + if (fseek(f, 0, SEEK_END)) + throw_testWinFuncFailed("fseek", (unsigned long)0); + + long size = ftell(f); + if (size < 0) + throw_testWinFuncFailed("ftell", (unsigned long)size); + if (size > 0x10000000) // sanity check limit to 256M + throw test::FuncFailed("read_small_file", "file size too large", + (unsigned long)size); + + if (fseek(f, 0, SEEK_SET)) + throw_testWinFuncFailed("fseek", (unsigned long)0); + + std::vector<char> content(static_cast<size_t>(size)); + content.resize(fread(content.data(), sizeof(char), content.size(), f)); + + return content; +} + +bool compare_files(const path& file1, const path& file2, bool binary) +{ + // TODO: if this is ever used for big file should read files in chunks + return read_small_file(file1, binary) == read_small_file(file2, binary); +} + +bool is_empty_folder(const path& dpath, bool or_doesnt_exist) +{ + bool isDir = false; + if (!winapi::ex::wide::fileExists(dpath.c_str(), &isDir)) + return or_doesnt_exist; + + if (!isDir) + return false; + + for (const auto& f : winapi::ex::wide::quickFindFiles(dpath.c_str(), L"*")) + if (f.fileName != L"." && f.fileName != L"..") + return false; + return true; +} + +void delete_file(const path& file) +{ + if (!DeleteFileW(file.c_str())) { + auto err = GetLastError(); + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw_testWinFuncFailed("DeleteFile", file.string()); + } +} + +void recursive_delete_files(const path& dpath) +{ + bool isDir = false; + if (!winapi::ex::wide::fileExists(dpath.c_str(), &isDir)) + return; + if (!isDir) + delete_file(dpath); + else { + // dpath exists and its a directory: + std::vector<std::wstring> recurse; + for (const auto& f : winapi::ex::wide::quickFindFiles(dpath.c_str(), L"*")) { + if (f.fileName == L"." || f.fileName == L"..") + continue; + if (f.attributes & FILE_ATTRIBUTE_DIRECTORY) + recurse.push_back(f.fileName); + else + delete_file(dpath / f.fileName); + } + for (auto r : recurse) + recursive_delete_files(dpath / r); + if (!RemoveDirectoryW(dpath.c_str())) + throw_testWinFuncFailed("RemoveDirectory", dpath.string().c_str()); + } + if (winapi::ex::wide::fileExists(dpath.c_str())) + throw FuncFailed("delete_directory_tree", dpath.string().c_str()); +} + +void recursive_copy_files(const path& src_path, const path& dest_path, bool overwrite) +{ + bool srcIsDir = false, destIsDir = false; + if (!winapi::ex::wide::fileExists(src_path.c_str(), &srcIsDir)) + throw FuncFailed("recursive_copy", "source doesn't exist", + src_path.string().c_str()); + if (!winapi::ex::wide::fileExists(dest_path.c_str(), &destIsDir) && srcIsDir) { + winapi::ex::wide::createPath(dest_path.c_str()); + destIsDir = true; + } + + if (!srcIsDir) + if (!destIsDir) { + if (!CopyFileW(src_path.c_str(), dest_path.c_str(), overwrite)) + throw_testWinFuncFailed( + "CopyFile", (src_path.string() + " => " + dest_path.string()).c_str()); + return; + } else + throw FuncFailed("recursive_copy", + "source is a file but destination is a directory", + (src_path.string() + ", " + dest_path.string()).c_str()); + + if (!destIsDir) + throw FuncFailed("recursive_copy", + "source is a directory but destination is a file", + (src_path.string() + ", " + dest_path.string()).c_str()); + + // source and destination are both directories: + std::vector<std::wstring> recurse; + for (const auto& f : winapi::ex::wide::quickFindFiles(src_path.c_str(), L"*")) { + if (f.fileName == L"." || f.fileName == L"..") + continue; + if (f.attributes & FILE_ATTRIBUTE_DIRECTORY) + recurse.push_back(f.fileName); + else if (!CopyFileW((src_path / f.fileName).c_str(), + (dest_path / f.fileName).c_str(), overwrite)) + throw_testWinFuncFailed("CopyFile", ((src_path / f.fileName).string() + " => " + + (dest_path / f.fileName).string())); + } + for (auto r : recurse) + recursive_copy_files(src_path / r, dest_path / r, overwrite); +} + +ScopedLoadLibrary::ScopedLoadLibrary(const wchar_t* dll_path) + : m_mod(LoadLibrary(dll_path)) +{} +ScopedLoadLibrary::~ScopedLoadLibrary() +{ + if (m_mod) + FreeLibrary(m_mod); +} + +}; // namespace test diff --git a/libs/usvfs/test/test_utils/test_helpers.h b/libs/usvfs/test/test_utils/test_helpers.h new file mode 100644 index 0000000..7ea8950 --- /dev/null +++ b/libs/usvfs/test/test_utils/test_helpers.h @@ -0,0 +1,165 @@ +#pragma once + +#include "windows_sane.h" + +#include <filesystem> +#include <format> +#include <memory> + +namespace test +{ + +class FuncFailed : public std::runtime_error +{ +public: + FuncFailed(const char* func) : std::runtime_error(msg(func)) {} + FuncFailed(const char* func, unsigned long res) + : std::runtime_error(msg(func, nullptr, &res)) + {} + FuncFailed(const char* func, const char* arg1) : std::runtime_error(msg(func, arg1)) + {} + FuncFailed(const char* func, const char* arg1, unsigned long res) + : std::runtime_error(msg(func, arg1, &res)) + {} + FuncFailed(const char* func, const char* what, const char* arg1) + : std::runtime_error(msg(func, arg1, nullptr, what)) + {} + FuncFailed(const char* func, const char* what, const char* arg1, unsigned long res) + : std::runtime_error(msg(func, arg1, &res, what)) + {} + +private: + std::string msg(std::string_view func, const char* arg1 = nullptr, + const unsigned long* res = nullptr, const char* what = nullptr); +}; + +class WinFuncFailed : public std::runtime_error +{ +public: + using runtime_error::runtime_error; +}; + +class WinFuncFailedGenerator +{ +public: + WinFuncFailedGenerator(DWORD gle = GetLastError()) : m_gle(gle) {} + WinFuncFailedGenerator(const WinFuncFailedGenerator&) = delete; + + DWORD lastError() const { return m_gle; } + + WinFuncFailed operator()(std::basic_string_view<char> func) + { + return WinFuncFailed(std::format("{} failed : lastError={}", func, m_gle)); + } + + WinFuncFailed operator()(std::basic_string_view<char> func, unsigned long res) + { + return WinFuncFailed( + std::format("{} failed : result=({:#x}), lastError={}", func, res, m_gle)); + } + + WinFuncFailed operator()(std::string_view func, std::basic_string_view<char> arg1) + { + return WinFuncFailed( + std::format("{} failed : {}, lastError={}", func, arg1, m_gle)); + } + + WinFuncFailed operator()(std::string_view func, std::basic_string_view<char> arg1, + unsigned long res) + { + return WinFuncFailed(std::format("{} failed : {}, result=({:#x}), lastError={}", + func, arg1, res, m_gle)); + } + +private: + DWORD m_gle; +}; + +// trick to guarantee the evalutation of GetLastError() before the evalution of the +// parameters to the WinFuncFailed message generation +template <class... Args> +[[noreturn]] void throw_testWinFuncFailed(std::string_view func, Args&&... args) +{ + ::test::WinFuncFailedGenerator exceptionGenerator; + throw exceptionGenerator(func, std::forward<Args>(args)...); +} + +class ScopedFILE +{ +public: + // try to open the given filepath with the given mode, if it fails, set err to + // the return code of _wfopen_s and return a nulled scoped file + // + static ScopedFILE open(const std::filesystem::path& filepath, std::wstring_view mode, + errno_t& err); + + // same as above but throw a WinFuncFailed() exception if opening the file failed + // + static ScopedFILE open(const std::filesystem::path& filepath, std::wstring_view mode); + +public: + ScopedFILE(FILE* f = nullptr) : m_f(f, &fclose) {} + + ScopedFILE(ScopedFILE&& other) noexcept = default; + ~ScopedFILE() = default; + + ScopedFILE(const ScopedFILE&) = delete; + + void close() { m_f = nullptr; } + + operator bool() const { return static_cast<bool>(m_f); } + operator FILE*() const { return m_f.get(); } + +private: + std::unique_ptr<FILE, decltype(&fclose)> m_f; +}; + +using std::filesystem::path; + +// path functions assume they are called by a test executable +// (calculate the requested path relative to the current executable path) + +path path_of_test_bin(const path& relative = path()); +path path_of_test_temp(const path& relative = path()); +path path_of_test_fixtures(const path& relative = path()); +path path_of_usvfs_lib(const path& relative = path()); + +std::string platform_dependant_executable(const char* name, const char* ext = "exe", + const char* platform = nullptr); + +// if full_path is a subfolder of base returns only the relative path, +// if full_path and base are the same folder "." is returned, +// otherwise full_path is returned unchanged +path path_as_relative(const path& base, const path& full_path); + +std::vector<char> read_small_file(const path& file, bool binary = true); + +// true iff the the contents of the two files is exactly the same +bool compare_files(const path& file1, const path& file2, bool binary = true); + +// return true iff the given path is an empty (optionally true also if path doesn't +// exist) +bool is_empty_folder(const path& dpath, bool or_doesnt_exist = false); + +void delete_file(const path& file); + +// Recursively deletes the given path and all the files and directories under it +// Use with care!!! +void recursive_delete_files(const path& dpath); + +// Recursively copies all files and directories from srcPath to destPath +void recursive_copy_files(const path& src_path, const path& dest_path, bool overwrite); + +class ScopedLoadLibrary +{ +public: + ScopedLoadLibrary(const wchar_t* dll_path); + ~ScopedLoadLibrary(); + + // returns zero if load library failed + operator HMODULE() const { return m_mod; } + +private: + HMODULE m_mod; +}; +}; // namespace test |
