forked from TanninOne/usvfs
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
403 additions
and
319 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#pragma once | ||
|
||
#include <filesystem> | ||
#include <format> | ||
|
||
#include <boost/algorithm/string.hpp> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
::testing::AssertionResult AssertDirectoryEquals(const std::filesystem::path& expected, | ||
const std::filesystem::path& actual, | ||
bool content = true) | ||
{ | ||
std::vector<std::string> failure_messages; | ||
std::vector<std::filesystem::path> in_both; | ||
|
||
// iterate left, check on right | ||
for (const auto& it : std::filesystem::recursive_directory_iterator{expected}) { | ||
const auto relpath = relative(it.path(), expected); | ||
if (!exists(actual / relpath)) { | ||
failure_messages.push_back( | ||
std::format("{} expected but not found", relpath.string())); | ||
} else { | ||
in_both.push_back(relpath); | ||
} | ||
} | ||
|
||
// iterate right, check on left | ||
for (const auto& it : std::filesystem::recursive_directory_iterator{actual}) { | ||
const auto relpath = relative(it.path(), actual); | ||
if (!exists(expected / relpath)) { | ||
failure_messages.push_back( | ||
std::format("{} found but not expected", relpath.string())); | ||
} | ||
} | ||
|
||
// check contents | ||
if (content) { | ||
for (const auto& relpath : in_both) { | ||
const auto expected_path = expected / relpath, actual_path = actual / relpath; | ||
|
||
if (is_directory(expected_path) != is_directory(actual_path)) { | ||
failure_messages.push_back( | ||
std::format("{} type mismatch, expected {} but found {}", relpath.string(), | ||
is_directory(expected_path) ? "directory" : "file", | ||
is_directory(expected_path) ? "file" : "directory")); | ||
continue; | ||
} | ||
|
||
if (is_directory(expected_path)) { | ||
continue; | ||
} | ||
|
||
if (!test::compare_files(expected_path, actual_path, true)) { | ||
failure_messages.push_back( | ||
std::format("{} content mismatch", relpath.string())); | ||
} | ||
} | ||
} | ||
|
||
if (failure_messages.empty()) { | ||
return ::testing::AssertionSuccess(); | ||
} | ||
|
||
return ::testing::AssertionFailure() | ||
<< "\n" | ||
<< boost::algorithm::join(failure_messages, "\n") << "\n"; | ||
} | ||
|
||
#define ASSERT_DIRECTORY_EQ(Expected, Actual) \ | ||
ASSERT_TRUE(AssertDirectoryEquals(Expected, Actual)) |
196 changes: 196 additions & 0 deletions
196
test/usvfs_global_test_runner/usvfs_global_test_fixture.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
#include "usvfs_global_test_fixture.h" | ||
|
||
#include <filesystem> | ||
#include <format> | ||
#include <fstream> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include <stringcast.h> | ||
#include <test_helpers.h> | ||
#include <usvfs.h> | ||
#include <windows_sane.h> | ||
|
||
// find the path to the executable that contains gtest entries | ||
// | ||
std::filesystem::path path_to_usvfs_global_test() | ||
{ | ||
return test::path_of_test_bin( | ||
test::platform_dependant_executable("usvfs_global_test", "exe")); | ||
} | ||
|
||
// path to the fixture for the given test group | ||
// | ||
std::filesystem::path path_to_usvfs_global_test_figures(std::wstring_view group) | ||
{ | ||
return test::path_of_test_fixtures() / "usvfs_global_test" / group; | ||
} | ||
|
||
// spawn the an hook version of the given | ||
// | ||
DWORD spawn_usvfs_hooked_process( | ||
const std::filesystem::path& app, const std::vector<std::wstring>& arguments = {}, | ||
const std::optional<std::filesystem::path>& working_directory = {}) | ||
{ | ||
using namespace usvfs::shared; | ||
|
||
std::wstring command = app; | ||
std::filesystem::path cwd = working_directory.value_or(app.parent_path()); | ||
std::vector<wchar_t> env; | ||
|
||
if (!arguments.empty()) { | ||
command += L" " + boost::algorithm::join(arguments, L" "); | ||
} | ||
|
||
STARTUPINFO si{0}; | ||
si.cb = sizeof(si); | ||
PROCESS_INFORMATION pi{0}; | ||
|
||
#pragma warning(push) | ||
#pragma warning(disable : 6387) | ||
|
||
if (!usvfsCreateProcessHooked(nullptr, command.data(), nullptr, nullptr, FALSE, 0, | ||
nullptr, cwd.c_str(), &si, &pi)) { | ||
test::throw_testWinFuncFailed( | ||
"CreateProcessHooked", | ||
string_cast<std::string>(command, CodePage::UTF8).c_str()); | ||
} | ||
|
||
WaitForSingleObject(pi.hProcess, INFINITE); | ||
|
||
DWORD exit = 99; | ||
if (!GetExitCodeProcess(pi.hProcess, &exit)) { | ||
test::WinFuncFailedGenerator failed; | ||
CloseHandle(pi.hProcess); | ||
CloseHandle(pi.hThread); | ||
throw failed("GetExitCodeProcess"); | ||
} | ||
|
||
CloseHandle(pi.hProcess); | ||
CloseHandle(pi.hThread); | ||
|
||
#pragma warning(pop) | ||
|
||
return exit; | ||
} | ||
|
||
class UsvfsGlobalTest::UsvfsGuard | ||
{ | ||
public: | ||
UsvfsGuard(std::string_view instance_name = "usvfs_test", bool logging = false) | ||
: m_parameters(usvfsCreateParameters(), &usvfsFreeParameters) | ||
{ | ||
usvfsSetInstanceName(m_parameters.get(), instance_name.data()); | ||
usvfsSetDebugMode(m_parameters.get(), false); | ||
usvfsSetLogLevel(m_parameters.get(), LogLevel::Debug); | ||
usvfsSetCrashDumpType(m_parameters.get(), CrashDumpsType::None); | ||
usvfsSetCrashDumpPath(m_parameters.get(), ""); | ||
|
||
usvfsInitLogging(logging); | ||
usvfsCreateVFS(m_parameters.get()); | ||
} | ||
|
||
~UsvfsGuard() { usvfsDisconnectVFS(); } | ||
|
||
private: | ||
std::unique_ptr<usvfsParameters, decltype(&usvfsFreeParameters)> m_parameters; | ||
}; | ||
|
||
UsvfsGlobalTest::UsvfsGlobalTest() | ||
: m_usvfs{std::make_unique<UsvfsGuard>()}, | ||
m_temporary_folder{test::path_of_test_temp()} | ||
{ | ||
std::string name{testing::UnitTest::GetInstance()->current_test_info()->name()}; | ||
m_group = {name.begin(), name.end()}; | ||
} | ||
|
||
UsvfsGlobalTest::~UsvfsGlobalTest() | ||
{ | ||
CleanUp(); | ||
} | ||
|
||
std::filesystem::path UsvfsGlobalTest::ActualFolder() const | ||
{ | ||
return m_temporary_folder; | ||
} | ||
|
||
std::filesystem::path UsvfsGlobalTest::ExpectedFolder() const | ||
{ | ||
return path_to_usvfs_global_test_figures(m_group) / "expected"; | ||
} | ||
|
||
void UsvfsGlobalTest::CleanUp() const | ||
{ | ||
if (exists(m_temporary_folder)) { | ||
remove_all(m_temporary_folder); | ||
} | ||
} | ||
|
||
void UsvfsGlobalTest::PrepareFileSystem() const | ||
{ | ||
// cleanup in case a previous tests failed to delete its stuff | ||
CleanUp(); | ||
|
||
// copy fixtures | ||
const auto fixtures = path_to_usvfs_global_test_figures(m_group) / "source"; | ||
if (exists(fixtures)) { | ||
copy(fixtures, m_temporary_folder, std::filesystem::copy_options::recursive); | ||
} | ||
} | ||
|
||
void UsvfsGlobalTest::SetUpOverwrite(bool force) const | ||
{ | ||
if (force && !exists(m_overwrite_folder)) { | ||
create_directory(m_overwrite_folder); | ||
} | ||
|
||
if (exists(m_overwrite_folder)) { | ||
usvfsVirtualLinkDirectoryStatic(m_overwrite_folder.c_str(), m_data_folder.c_str(), | ||
LINKFLAG_CREATETARGET | LINKFLAG_RECURSIVE); | ||
} | ||
} | ||
|
||
void UsvfsGlobalTest::PreapreMapping() const | ||
{ | ||
// should not be needed, but just to be safe | ||
usvfsClearVirtualMappings(); | ||
|
||
if (!exists(m_data_folder)) { | ||
throw std::runtime_error{ | ||
std::format("data path missing at {}", m_data_folder.string())}; | ||
} | ||
|
||
if (exists(m_mods_folder)) { | ||
for (const auto& mod : std::filesystem::directory_iterator(m_mods_folder)) { | ||
if (!is_directory(mod)) { | ||
continue; | ||
} | ||
usvfsVirtualLinkDirectoryStatic(mod.path().c_str(), m_data_folder.c_str(), | ||
LINKFLAG_RECURSIVE); | ||
} | ||
} | ||
|
||
// by default, only create overwrite if present | ||
SetUpOverwrite(false); | ||
} | ||
|
||
int UsvfsGlobalTest::Run() const | ||
{ | ||
const auto res = spawn_usvfs_hooked_process( | ||
path_to_usvfs_global_test(), {std::format(L"--gtest_filter={}.*", m_group), | ||
L"--gtest_brief=1", m_data_folder.native()}); | ||
|
||
// TODO: try to do this with gtest itself? | ||
if (res != 0) { | ||
const auto log_path = test::path_of_test_bin(m_group + L".log"); | ||
std::ofstream os{log_path}; | ||
std::string buffer(1024, '\0'); | ||
std::cout << "process returned " << std::hex << res << ", usvfs logs dumped to " | ||
<< log_path.string() << '\n'; | ||
while (usvfsGetLogMessages(buffer.data(), buffer.size(), false)) { | ||
os << " " << buffer.c_str() << "\n"; | ||
} | ||
} | ||
|
||
return res; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#pragma once | ||
|
||
#include <filesystem> | ||
#include <memory> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
class UsvfsGlobalTest : public testing::Test | ||
{ | ||
public: | ||
UsvfsGlobalTest(); | ||
|
||
void SetUp() override | ||
{ | ||
PrepareFileSystem(); | ||
PreapreMapping(); | ||
} | ||
|
||
void TearDown() override { CleanUp(); } | ||
|
||
~UsvfsGlobalTest(); | ||
|
||
// setup the overwrite folder | ||
// | ||
// if force is true, force creation of the overwrite folder even if not present, this | ||
// is useful when the overwrite is initially empty and thus cannot be committed to git | ||
// but need to contain files after the run | ||
// | ||
void SetUpOverwrite(bool force = true) const; | ||
|
||
// run the test, return the exit code of the google test process | ||
// | ||
int Run() const; | ||
|
||
// return the path to the folder containing the expected results | ||
// | ||
std::filesystem::path ActualFolder() const; | ||
|
||
// return the path to the folder containing the expected results | ||
// | ||
std::filesystem::path ExpectedFolder() const; | ||
|
||
private: | ||
class UsvfsGuard; | ||
|
||
// prepare the filesystem by copying files and folders from the relevant fixtures | ||
// folder to the temporary folder | ||
// | ||
// after this operations, the temporary folder will contain | ||
// - a data folder | ||
// - [optional] a mods folder containing a set of folders that should be mounted | ||
// - [optional] an overwrite folder that should be mounted as overwrite | ||
// | ||
void PrepareFileSystem() const; | ||
|
||
// prepare mapping using the given set of paths | ||
// | ||
void PreapreMapping() const; | ||
|
||
// cleanup the temporary path | ||
// | ||
void CleanUp() const; | ||
|
||
// usvfs_guard | ||
std::unique_ptr<UsvfsGuard> m_usvfs; | ||
|
||
// name of GTest group (first argument of the TEST macro) to run | ||
std::wstring m_group; | ||
|
||
// path to the folder containing temporary files | ||
std::filesystem::path m_temporary_folder; | ||
|
||
// path to the subfolder inside the temporary folder | ||
std::filesystem::path m_data_folder = m_temporary_folder / "data"; | ||
std::filesystem::path m_mods_folder = m_temporary_folder / "mods"; | ||
std::filesystem::path m_overwrite_folder = m_temporary_folder / "overwrite"; | ||
}; |
Oops, something went wrong.