aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/test/test_utils
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/test/test_utils')
-rw-r--r--libs/usvfs/test/test_utils/CMakeLists.txt11
-rw-r--r--libs/usvfs/test/test_utils/test_helpers.cpp248
-rw-r--r--libs/usvfs/test/test_utils/test_helpers.h165
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