mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Fix conversions between strings and paths
The standard library path constructor assumes the string is in the platform's native encoding, but for LOOT all strings are in UTF-8.
This commit is contained in:
+2
-3
@@ -128,7 +128,7 @@ set(PSEUDOSEM_INCLUDE_DIRS "${SOURCE_DIR}/include")
|
||||
ExternalProject_Add(testing-metadata
|
||||
PREFIX "external"
|
||||
GIT_REPOSITORY "https://github.com/loot/testing-metadata"
|
||||
GIT_TAG "1.3.0"
|
||||
GIT_TAG "1.4.0"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND "")
|
||||
@@ -253,8 +253,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/git_helper.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/crc.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/logging.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/version.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/windows_encoding_converters.h")
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/version.h")
|
||||
|
||||
set (LOOT_TESTS_SRC "${CMAKE_SOURCE_DIR}/src/tests/api/internals/main.cpp")
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ void Game::CacheArchives() {
|
||||
// file extension with the archive extension resolves to the same file.
|
||||
// Could use boost::iends_with, but it's less obvious here that the
|
||||
// test string is ASCII-only.
|
||||
if (boost::iequals(it->path().extension().string(), archiveFileExtension)) {
|
||||
if (boost::iequals(it->path().extension().u8string(), archiveFileExtension)) {
|
||||
cache_->CacheArchivePath(it->path());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ void GitHelper::Clone(const std::filesystem::path& path,
|
||||
if (logger_) {
|
||||
logger_->trace("Target repo path not empty, cloning into temporary directory.");
|
||||
}
|
||||
auto directory = "LOOT-" + path.filename().string() + "-" +
|
||||
auto directory = "LOOT-" + path.filename().u8string() + "-" +
|
||||
boost::lexical_cast<std::string>((boost::uuids::random_generator())());
|
||||
repoPath = fs::temp_directory_path() / directory;
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
#include <pseudosem.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "api/helpers/windows_encoding_converters.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
@@ -97,14 +95,14 @@ Version::Version(const std::string& ver) {
|
||||
Version::Version(const std::filesystem::path& file) {
|
||||
#ifdef _WIN32
|
||||
DWORD dummy = 0;
|
||||
DWORD size = GetFileVersionInfoSize(ToWinWide(file.string()).c_str(), &dummy);
|
||||
DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
|
||||
|
||||
if (size > 0) {
|
||||
LPBYTE point = new BYTE[size];
|
||||
UINT uLen;
|
||||
VS_FIXEDFILEINFO* info;
|
||||
|
||||
GetFileVersionInfo(ToWinWide(file.string()).c_str(), 0, size, point);
|
||||
GetFileVersionInfo(file.wstring().c_str(), 0, size, point);
|
||||
|
||||
VerQueryValue(point, L"\\", (LPVOID*)&info, &uLen);
|
||||
|
||||
@@ -122,11 +120,11 @@ Version::Version(const std::filesystem::path& file) {
|
||||
#else
|
||||
// ensure filename has no quote characters in it to avoid command injection
|
||||
// attacks
|
||||
if (std::string::npos != file.string().find('"')) {
|
||||
if (std::string::npos != file.u8string().find('"')) {
|
||||
// command mostly borrowed from the gnome-exe-thumbnailer.sh script
|
||||
// wrestool is part of the icoutils package
|
||||
std::string cmd =
|
||||
"wrestool --extract --raw --type=version \"" + file.string() +
|
||||
"wrestool --extract --raw --type=version \"" + file.u8string() +
|
||||
"\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' "
|
||||
"| sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2012-2016 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOOT_API_HELPERS_WINDOWS_ENCODING_CONVERTERS
|
||||
#define LOOT_API_HELPERS_WINDOWS_ENCODING_CONVERTERS
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
#ifndef _UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
#include "shlobj.h"
|
||||
#include "shlwapi.h"
|
||||
#include "windows.h"
|
||||
|
||||
namespace loot {
|
||||
/**
|
||||
* Convert a UTF-8 std::string to a UTF-16 std::wstring.
|
||||
*
|
||||
* This isn't strictly part of the LOOT API, but is used within the API and the
|
||||
* LOOT application, so is shared through the API.
|
||||
* @param str
|
||||
* A string encoded in UTF-8.
|
||||
* @return A wstring encoded in UTF-16.
|
||||
*/
|
||||
inline std::wstring ToWinWide(const std::string& str) {
|
||||
size_t len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), 0, 0);
|
||||
std::wstring wstr(len, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], len);
|
||||
return wstr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UTF-16 std::wstring to a UTF-8 std::string.
|
||||
*
|
||||
* This isn't strictly part of the LOOT API, but is used within the API and the
|
||||
* LOOT application, so is shared through the API.
|
||||
* @param wstr
|
||||
* A wstring encoded in UTF-16.
|
||||
* @return A string encoded in UTF-8.
|
||||
*/
|
||||
inline std::string FromWinWide(const std::wstring& wstr) {
|
||||
size_t len = WideCharToMultiByte(
|
||||
CP_UTF8, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL);
|
||||
std::string str(len, 0);
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8, 0, wstr.c_str(), wstr.length(), &str[0], len, NULL, NULL);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -66,7 +66,7 @@ MasterlistInfo Masterlist::GetInfo(const std::filesystem::path& path,
|
||||
logger->trace("Diffing masterlist HEAD and working copy.");
|
||||
}
|
||||
info.is_modified =
|
||||
GitHelper::IsFileDifferent(path.parent_path(), path.filename().string());
|
||||
GitHelper::IsFileDifferent(path.parent_path(), path.filename().u8string());
|
||||
|
||||
return info;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ bool Masterlist::Update(const std::filesystem::path& path,
|
||||
GitHelper git;
|
||||
auto logger = getLogger();
|
||||
fs::path repoPath = path.parent_path();
|
||||
string filename = path.filename().string();
|
||||
string filename = path.filename().u8string();
|
||||
|
||||
if (path.empty() || repoUrl.empty() || repoBranch.empty())
|
||||
throw std::invalid_argument("Repository path, URL and branch must not be empty.");
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "api/metadata/condition_grammar.h"
|
||||
#include "loot/exception/condition_syntax_error.h"
|
||||
|
||||
using std::filesystem::u8path;
|
||||
|
||||
namespace loot {
|
||||
ConditionEvaluator::ConditionEvaluator() :
|
||||
gameType_(GameType::tes4),
|
||||
@@ -163,10 +165,10 @@ bool ConditionEvaluator::fileExists(const std::string& filePath) const {
|
||||
|
||||
// Not a loaded plugin, check the filesystem.
|
||||
if (hasPluginFileExtension(filePath, gameType_))
|
||||
return std::filesystem::exists(dataPath_ / filePath) ||
|
||||
std::filesystem::exists(dataPath_ / (filePath + ".ghost"));
|
||||
return std::filesystem::exists(dataPath_ / u8path(filePath)) ||
|
||||
std::filesystem::exists(dataPath_ / u8path(filePath + ".ghost"));
|
||||
else
|
||||
return std::filesystem::exists(dataPath_ / filePath);
|
||||
return std::filesystem::exists(dataPath_ / u8path(filePath));
|
||||
}
|
||||
|
||||
bool ConditionEvaluator::regexMatchExists(
|
||||
@@ -295,7 +297,7 @@ std::filesystem::path ConditionEvaluator::getRegexParentPath(
|
||||
if (pos == std::string::npos)
|
||||
return std::filesystem::path();
|
||||
|
||||
return std::filesystem::path(regexString.substr(0, pos));
|
||||
return u8path(regexString.substr(0, pos));
|
||||
}
|
||||
|
||||
std::string ConditionEvaluator::getRegexFilename(
|
||||
@@ -359,7 +361,7 @@ bool ConditionEvaluator::isRegexMatchInDataDirectory(
|
||||
std::filesystem::directory_iterator(dataPath_ / pathRegex.first),
|
||||
std::filesystem::directory_iterator(),
|
||||
[&](const std::filesystem::directory_entry& entry) {
|
||||
const std::string filename = entry.path().filename().string();
|
||||
const std::string filename = entry.path().filename().u8string();
|
||||
return std::regex_match(filename, pathRegex.second) &&
|
||||
condition(filename);
|
||||
});
|
||||
@@ -425,7 +427,7 @@ Version ConditionEvaluator::getVersion(const std::string& filePath) const {
|
||||
.GetVersion()
|
||||
.value_or(""));
|
||||
|
||||
return Version(dataPath_ / filePath);
|
||||
return Version(dataPath_ / u8path(filePath));
|
||||
}
|
||||
}
|
||||
bool ConditionEvaluator::shouldParseOnly() const {
|
||||
@@ -453,12 +455,12 @@ uint32_t ConditionEvaluator::getCrc(const std::string & file) const {
|
||||
|
||||
// Otherwise calculate it from the file.
|
||||
if (crc == 0) {
|
||||
if (std::filesystem::exists(dataPath_ / file)) {
|
||||
crc = GetCrc32(dataPath_ / file);
|
||||
if (std::filesystem::exists(dataPath_ / u8path(file))) {
|
||||
crc = GetCrc32(dataPath_ / u8path(file));
|
||||
}
|
||||
else if (hasPluginFileExtension(file, gameType_) &&
|
||||
std::filesystem::exists(dataPath_ / (file + ".ghost"))) {
|
||||
crc = GetCrc32(dataPath_ / (file + ".ghost"));
|
||||
std::filesystem::exists(dataPath_ / u8path(file + ".ghost"))) {
|
||||
crc = GetCrc32(dataPath_ / u8path(file + ".ghost"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -55,7 +55,7 @@ Plugin::Plugin(const GameType gameType,
|
||||
auto logger = getLogger();
|
||||
|
||||
try {
|
||||
std::filesystem::path filepath = dataPath / name_;
|
||||
std::filesystem::path filepath = dataPath / std::filesystem::u8path(name_);
|
||||
|
||||
// In case the plugin is ghosted.
|
||||
if (!std::filesystem::exists(filepath)) {
|
||||
@@ -239,7 +239,7 @@ bool Plugin::IsValid(const std::string& filename,
|
||||
return false;
|
||||
|
||||
bool isValid;
|
||||
auto path = dataPath / filename;
|
||||
auto path = dataPath / std::filesystem::u8path(filename);
|
||||
int ret = esp_plugin_is_valid(
|
||||
GetEspluginGameId(gameType), path.u8string().c_str(), true, &isValid);
|
||||
|
||||
@@ -255,7 +255,7 @@ bool Plugin::IsValid(const std::string& filename,
|
||||
|
||||
uintmax_t Plugin::GetFileSize(const std::string& filename,
|
||||
const std::filesystem::path& dataPath) {
|
||||
std::filesystem::path realPath = dataPath / filename;
|
||||
std::filesystem::path realPath = dataPath / std::filesystem::u8path(filename);
|
||||
if (!std::filesystem::exists(realPath))
|
||||
realPath += ".ghost";
|
||||
|
||||
@@ -323,16 +323,15 @@ bool Plugin::LoadsArchive(const std::string& pluginName,
|
||||
|
||||
if (gameType == GameType::tes5) {
|
||||
// Skyrim plugins only load BSAs that exactly match their basename.
|
||||
return std::filesystem::exists(
|
||||
dataPath /
|
||||
(pluginName.substr(0, pluginName.length() - 4) + archiveExtension));
|
||||
auto filename = pluginName.substr(0, pluginName.length() - 4) + archiveExtension;
|
||||
return std::filesystem::exists(dataPath / std::filesystem::u8path(filename));
|
||||
} else if (gameType != GameType::tes4 ||
|
||||
boost::iends_with(pluginName, ".esp")) {
|
||||
// Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which
|
||||
// begin with the plugin basename.
|
||||
string basename = pluginName.substr(0, pluginName.length() - 4);
|
||||
for (const auto& archivePath : gameCache->GetArchivePaths()) {
|
||||
if (boost::istarts_with(archivePath.filename().string(), basename)) {
|
||||
if (boost::istarts_with(archivePath.filename().u8string(), basename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,32 @@ along with LOOT. If not, see
|
||||
namespace loot {
|
||||
namespace test {
|
||||
#ifdef _WIN32
|
||||
class VersionTest : public CommonGameTestFixture {
|
||||
protected:
|
||||
VersionTest() : nonAsciiDll(u8"loot_ap\u00ED.dll") {}
|
||||
|
||||
void SetUp() {
|
||||
CommonGameTestFixture::SetUp();
|
||||
|
||||
ASSERT_NO_THROW(std::filesystem::copy_file("loot_api.dll",
|
||||
dataPath / std::filesystem::u8path(nonAsciiDll)));
|
||||
}
|
||||
|
||||
const std::string nonAsciiDll;
|
||||
};
|
||||
|
||||
// Pass an empty first argument, as it's a prefix for the test instantation,
|
||||
// but we only have the one so no prefix is necessary.
|
||||
// Just test with one game because if it works for one it will work for them
|
||||
// all.
|
||||
INSTANTIATE_TEST_CASE_P(, VersionTest, ::testing::Values(GameType::tes5));
|
||||
|
||||
TEST_P(VersionTest, shouldExtractVersionFromNonAsciiDll) {
|
||||
Version version(dataPath / std::filesystem::u8path(nonAsciiDll));
|
||||
std::string expected(LootVersion::string() + ".0");
|
||||
EXPECT_EQ(expected, version.AsString());
|
||||
}
|
||||
|
||||
TEST(Version, shouldExtractVersionFromApiDll) {
|
||||
// Use the API DLL built.
|
||||
Version version(std::filesystem::path("loot_api.dll"));
|
||||
@@ -40,6 +66,7 @@ TEST(Version, shouldExtractVersionFromApiDll) {
|
||||
EXPECT_EQ(expected, version.AsString());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Version, defaultConstructorShouldSetEmptyVersionString) {
|
||||
EXPECT_EQ("", Version().AsString());
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ protected:
|
||||
repoBranch("master"),
|
||||
oldBranch("old-branch"),
|
||||
repoUrl("https://github.com/loot/testing-metadata.git"),
|
||||
masterlistPath(localPath / "masterlist.yaml") {}
|
||||
masterlistPath(localPath / "masterlist.yaml"),
|
||||
nonAsciiMasterlistPath(localPath / std::filesystem::u8path(u8"masterl\u00EDst.yaml")) {}
|
||||
|
||||
void SetUp() {
|
||||
CommonGameTestFixture::SetUp();
|
||||
@@ -48,14 +49,23 @@ protected:
|
||||
ASSERT_TRUE(std::filesystem::exists(metadataFilesPath / "masterlist.yaml"));
|
||||
|
||||
ASSERT_FALSE(std::filesystem::exists(masterlistPath));
|
||||
ASSERT_FALSE(std::filesystem::exists(nonAsciiMasterlistPath));
|
||||
ASSERT_FALSE(std::filesystem::exists(localPath / ".git"));
|
||||
}
|
||||
|
||||
void runRepoCommand(const std::string& command) {
|
||||
auto testPath = std::filesystem::current_path();
|
||||
std::filesystem::current_path(masterlistPath.parent_path());
|
||||
system(command.c_str());
|
||||
std::filesystem::current_path(testPath);
|
||||
}
|
||||
|
||||
const std::string repoUrl;
|
||||
const std::string repoBranch;
|
||||
const std::string oldBranch;
|
||||
|
||||
const std::filesystem::path masterlistPath;
|
||||
const std::filesystem::path nonAsciiMasterlistPath;
|
||||
};
|
||||
|
||||
// Pass an empty first argument, as it's a prefix for the test instantation,
|
||||
@@ -125,16 +135,23 @@ TEST_P(MasterlistTest, updateShouldReturnFalseIfAnUpToDateMasterlistExists) {
|
||||
EXPECT_FALSE(masterlist.Update(masterlistPath, repoUrl, repoBranch));
|
||||
}
|
||||
|
||||
TEST_P(MasterlistTest, updateShouldReturnFalseIfAnUpToDateMasterlistWithANonAsciiFilenameExists) {
|
||||
Masterlist masterlist;
|
||||
|
||||
EXPECT_TRUE(masterlist.Update(nonAsciiMasterlistPath, repoUrl, repoBranch));
|
||||
EXPECT_TRUE(std::filesystem::exists(nonAsciiMasterlistPath));
|
||||
|
||||
EXPECT_FALSE(masterlist.Update(nonAsciiMasterlistPath, repoUrl, repoBranch));
|
||||
EXPECT_TRUE(std::filesystem::exists(nonAsciiMasterlistPath));
|
||||
}
|
||||
|
||||
TEST_P(MasterlistTest,
|
||||
updateShouldDiscardLocalHistoryIfRemoteHistoryIsDifferent) {
|
||||
Masterlist masterlist;
|
||||
ASSERT_TRUE(masterlist.Update(masterlistPath, repoUrl, repoBranch));
|
||||
|
||||
auto testPath = std::filesystem::current_path();
|
||||
std::filesystem::current_path(masterlistPath.parent_path());
|
||||
system("git config commit.gpgsign false");
|
||||
system("git commit --amend -m \"changing local history\"");
|
||||
std::filesystem::current_path(testPath);
|
||||
runRepoCommand("git config commit.gpgsign false");
|
||||
runRepoCommand("git commit --amend -m \"changing local history\"");
|
||||
|
||||
EXPECT_TRUE(masterlist.Update(masterlistPath, repoUrl, repoBranch));
|
||||
}
|
||||
@@ -192,6 +209,24 @@ TEST_P(
|
||||
EXPECT_TRUE(info.is_modified);
|
||||
}
|
||||
|
||||
TEST_P(MasterlistTest,
|
||||
getInfoShouldDetectWhenAMasterlistWithANonAsciiFilenameHasBeenEdited) {
|
||||
Masterlist masterlist;
|
||||
ASSERT_TRUE(masterlist.Update(masterlistPath, repoUrl, repoBranch));
|
||||
|
||||
auto nonAsciiPath = masterlistPath.parent_path() / std::filesystem::u8path(u8"non\u00C1scii.yaml");
|
||||
std::filesystem::copy_file(masterlistPath, nonAsciiPath);
|
||||
|
||||
runRepoCommand("git add " + nonAsciiPath.string());
|
||||
std::ofstream out(nonAsciiPath);
|
||||
out.close();
|
||||
|
||||
MasterlistInfo info = masterlist.GetInfo(nonAsciiPath, false);
|
||||
EXPECT_EQ(40, info.revision_id.length());
|
||||
EXPECT_EQ(10, info.revision_date.length());
|
||||
EXPECT_TRUE(info.is_modified);
|
||||
}
|
||||
|
||||
TEST_P(MasterlistTest,
|
||||
isLatestShouldThrowIfTheGivenPathDoesNotBelongToAGitRepository) {
|
||||
ASSERT_NO_THROW(std::filesystem::copy(metadataFilesPath / "masterlist.yaml",
|
||||
|
||||
@@ -42,9 +42,29 @@ protected:
|
||||
evaluator_(game_.Type(),
|
||||
game_.DataPath(),
|
||||
game_.GetCache(),
|
||||
game_.GetLoadOrderHandler()) {}
|
||||
game_.GetLoadOrderHandler()),
|
||||
nonAsciiEsm(u8"non\u00C1scii.esm"),
|
||||
nonAsciiNestedFile(u8"non\u00C1scii/test.txt") {
|
||||
// Make sure the plugin with a non-ASCII filename exists.
|
||||
std::filesystem::copy_file(dataPath / blankEsm,
|
||||
dataPath / std::filesystem::u8path(nonAsciiEsm));
|
||||
|
||||
auto nonAsciiPath = dataPath / std::filesystem::u8path(nonAsciiNestedFile);
|
||||
std::filesystem::create_directory(nonAsciiPath.parent_path());
|
||||
|
||||
std::ofstream out(nonAsciiPath);
|
||||
out.close();
|
||||
}
|
||||
|
||||
std::string IntToHexString(const uint32_t value) {
|
||||
std::stringstream stream;
|
||||
stream << std::hex << value;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
const std::vector<MessageContent> info_;
|
||||
const std::string nonAsciiEsm;
|
||||
const std::string nonAsciiNestedFile;
|
||||
|
||||
Game game_;
|
||||
ConditionEvaluator evaluator_;
|
||||
@@ -75,6 +95,36 @@ TEST_P(ConditionEvaluatorTest,
|
||||
EXPECT_TRUE(evaluator_.evaluate("file(\"" + blankEsm + "\")"));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateFileConditionShouldReturnTrueForANonAsciiFileThatExists) {
|
||||
EXPECT_TRUE(evaluator_.evaluate("file(\"" + nonAsciiEsm + "\")"));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateChecksumConditionShouldBeAbleToGetTheCrcOfANonAsciiFile) {
|
||||
std::string condition("checksum(\"" + nonAsciiEsm + "\", " +
|
||||
IntToHexString(blankEsmCrc) + ")");
|
||||
EXPECT_TRUE(evaluator_.evaluate(condition));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateVersionConditionShouldBeAbleToGetTheVersionOfANonAsciiFile) {
|
||||
std::string condition("version(\"" + nonAsciiEsm + "\", \"5.0\", ==)");
|
||||
EXPECT_TRUE(evaluator_.evaluate(condition));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateRegexFileConditionShouldReturnTrueForANonAsciiFileThatExists) {
|
||||
std::string condition(u8"file(\"non\u00C1scii.*\\.esm\")");
|
||||
EXPECT_TRUE(evaluator_.evaluate(condition));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateRegexFileConditionShouldReturnTrueForANonAsciiNestedFileThatExists) {
|
||||
std::string condition(u8"file(\"non\u00C1scii/.+\\.txt\")");
|
||||
EXPECT_TRUE(evaluator_.evaluate(condition));
|
||||
}
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateShouldReturnFalseForAConditionThatIsFalse) {
|
||||
EXPECT_FALSE(evaluator_.evaluate("file(\"" + missingEsp + "\")"));
|
||||
@@ -88,6 +138,14 @@ TEST_P(
|
||||
EXPECT_TRUE(evaluator_.evaluate(dirtyInfo, blankEsm));
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
ConditionEvaluatorTest,
|
||||
evaluateShouldBeTrueIfTheCrcInTheCleaningDataMatchesTheCrcOfANonAsciiPlugin) {
|
||||
PluginCleaningData dirtyInfo(blankEsmCrc, "cleaner", info_, 2, 10, 30);
|
||||
|
||||
EXPECT_TRUE(evaluator_.evaluate(dirtyInfo, nonAsciiEsm));
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
ConditionEvaluatorTest,
|
||||
evaluateShouldBeFalseIfTheCrcInThePluginCleaningDataGivenDoesNotMatchTheRealPluginCrc) {
|
||||
|
||||
@@ -37,6 +37,7 @@ protected:
|
||||
PluginTest() :
|
||||
emptyFile("EmptyFile.esm"),
|
||||
lowercaseBlankEsp("blank.esp"),
|
||||
nonAsciiEsp(u8"non\u00C1scii.esp"),
|
||||
game_(GetParam(), dataPath.parent_path(), localPath),
|
||||
blankArchive("Blank" + GetArchiveFileExtension(game_.Type())),
|
||||
blankSuffixArchive("Blank - Different - suffix" +
|
||||
@@ -57,6 +58,10 @@ protected:
|
||||
dataPath / lowercaseBlankEsp));
|
||||
#endif
|
||||
|
||||
// Make sure the plugin with a non-ASCII filename exists.
|
||||
ASSERT_NO_THROW(std::filesystem::copy_file(dataPath / blankEsp,
|
||||
dataPath / std::filesystem::u8path(nonAsciiEsp)));
|
||||
|
||||
if (GetParam() != GameType::fo4 && GetParam() != GameType::tes5se) {
|
||||
ASSERT_NO_THROW(
|
||||
std::filesystem::copy(dataPath / blankEsp, dataPath / blankEsl));
|
||||
@@ -68,14 +73,34 @@ protected:
|
||||
out.open(dataPath / blankSuffixArchive);
|
||||
out.close();
|
||||
|
||||
auto nonAsciiArchivePath = dataPath / std::filesystem::u8path(u8"non\u00C1scii" + GetArchiveFileExtension(game_.Type()));
|
||||
out.open(nonAsciiArchivePath);
|
||||
out.close();
|
||||
|
||||
game_.GetCache()->CacheArchivePath(dataPath / blankArchive);
|
||||
game_.GetCache()->CacheArchivePath(dataPath / blankSuffixArchive);
|
||||
game_.GetCache()->CacheArchivePath(dataPath / nonAsciiArchivePath);
|
||||
}
|
||||
|
||||
uintmax_t getGhostedPluginFileSize() {
|
||||
if (GetParam() == GameType::tes4)
|
||||
return 390;
|
||||
else
|
||||
return 1358;
|
||||
}
|
||||
|
||||
uintmax_t getNonAsciiEspFileSize() {
|
||||
if (GetParam() == GameType::tes4)
|
||||
return 55;
|
||||
else
|
||||
return 1019;
|
||||
}
|
||||
|
||||
Game game_;
|
||||
|
||||
const std::string emptyFile;
|
||||
const std::string lowercaseBlankEsp;
|
||||
const std::string nonAsciiEsp;
|
||||
const std::string blankArchive;
|
||||
const std::string blankSuffixArchive;
|
||||
|
||||
@@ -117,6 +142,18 @@ INSTANTIATE_TEST_CASE_P(,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
|
||||
TEST_P(PluginTest, loadingShouldHandleNonAsciiFilenamesCorrectly) {
|
||||
Plugin plugin(game_.Type(),
|
||||
game_.DataPath(),
|
||||
game_.GetCache(),
|
||||
game_.GetLoadOrderHandler(),
|
||||
nonAsciiEsp,
|
||||
true);
|
||||
|
||||
EXPECT_EQ(nonAsciiEsp, plugin.GetName());
|
||||
EXPECT_EQ(nonAsciiEsp, plugin.GetName());
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) {
|
||||
Plugin plugin(game_.Type(),
|
||||
game_.DataPath(),
|
||||
@@ -259,16 +296,28 @@ TEST_P(
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesAnEspFileBasenameShouldReturnTrue) {
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesANonAsciiEspFileBasenameShouldReturnTrue) {
|
||||
EXPECT_TRUE(Plugin(game_.Type(),
|
||||
game_.DataPath(),
|
||||
game_.GetCache(),
|
||||
game_.GetLoadOrderHandler(),
|
||||
blankEsp,
|
||||
nonAsciiEsp,
|
||||
true)
|
||||
.LoadsArchive());
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesAnEspFileBasenameShouldReturnTrue) {
|
||||
EXPECT_TRUE(Plugin(game_.Type(),
|
||||
game_.DataPath(),
|
||||
game_.GetCache(),
|
||||
game_.GetLoadOrderHandler(),
|
||||
blankEsp,
|
||||
true)
|
||||
.LoadsArchive());
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivionAndSkyrim) {
|
||||
@@ -318,6 +367,10 @@ TEST_P(PluginTest, isValidShouldReturnTrueForAValidPlugin) {
|
||||
EXPECT_TRUE(Plugin::IsValid(blankEsm, game_.Type(), game_.DataPath()));
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, isValidShouldReturnTrueForAValidNonAsciiPlugin) {
|
||||
EXPECT_TRUE(Plugin::IsValid(nonAsciiEsp, game_.Type(), game_.DataPath()));
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, isValidShouldReturnFalseForANonPluginFile) {
|
||||
EXPECT_FALSE(Plugin::IsValid(nonPluginFile, game_.Type(), game_.DataPath()));
|
||||
}
|
||||
@@ -326,6 +379,18 @@ TEST_P(PluginTest, isValidShouldReturnFalseForAnEmptyFile) {
|
||||
EXPECT_FALSE(Plugin::IsValid(emptyFile, game_.Type(), game_.DataPath()));
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, getFileSizeShouldThrowForAMissingPlugin) {
|
||||
EXPECT_THROW(Plugin::GetFileSize(missingEsp, game_.DataPath()), std::filesystem::filesystem_error);
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, getFileSizeShouldReturnCorrectValueForAPlugin) {
|
||||
EXPECT_EQ(getNonAsciiEspFileSize(), Plugin::GetFileSize(nonAsciiEsp, game_.DataPath()));
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, getFileSizeShouldReturnCorrectValueForAGhostedPlugin) {
|
||||
EXPECT_EQ(getGhostedPluginFileSize(), Plugin::GetFileSize(blankMasterDependentEsm, game_.DataPath()));
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, isActiveShouldReturnTrueForAPluginThatIsActive) {
|
||||
EXPECT_TRUE(Plugin(game_.Type(),
|
||||
game_.DataPath(),
|
||||
|
||||
Reference in New Issue
Block a user