From 1c025a4fba60208086bbfbc87cb77ed41ada362f Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 21 Oct 2018 10:16:53 +0100 Subject: [PATCH] Replace condition parsing and evaluation code With loot-condition-interpreter dependency. This also replaces the pseudosem dependency, condition and CRC caching, and executable version extraction. --- CMakeLists.txt | 31 +- src/api/api_database.cpp | 16 +- src/api/api_database.h | 8 +- src/api/game/game.cpp | 8 +- src/api/game/game.h | 2 + src/api/game/game_cache.cpp | 43 -- src/api/game/game_cache.h | 10 - src/api/game/load_order_handler.cpp | 20 + src/api/game/load_order_handler.h | 2 + src/api/helpers/version.cpp | 100 +-- src/api/helpers/version.h | 24 +- src/api/metadata/condition_evaluator.cpp | 493 ++++-------- src/api/metadata/condition_evaluator.h | 69 +- src/api/metadata/condition_grammar.h | 304 -------- src/api/metadata/conditional_metadata.cpp | 6 +- src/api/metadata_list.cpp | 8 +- src/api/metadata_list.h | 2 +- src/api/plugin.cpp | 7 +- .../api/internals/game/game_cache_test.h | 50 -- .../internals/game/load_order_handler_test.h | 32 + .../api/internals/helpers/version_test.h | 205 +---- src/tests/api/internals/main.cpp | 1 - .../metadata/condition_evaluator_test.h | 94 +-- .../metadata/condition_grammar_test.h | 727 ------------------ src/tests/api/internals/metadata_list_test.h | 6 +- 25 files changed, 335 insertions(+), 1933 deletions(-) delete mode 100644 src/api/metadata/condition_grammar.h delete mode 100644 src/tests/api/internals/metadata/condition_grammar_test.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e43cd3b..2f2481e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,14 +116,21 @@ ELSE () set (LIBLOADORDER_LIBRARIES ${LIBLOADORDER_LIBRARIES} dl) ENDIF () -ExternalProject_Add(pseudosem +ExternalProject_Add(loot-condition-interpreter PREFIX "external" - URL "https://github.com/WrinklyNinja/pseudosem/archive/1.1.0.tar.gz" + URL "https://github.com/Ortham/loot-condition-interpreter/archive/1.0.0.tar.gz" CONFIGURE_COMMAND "" - BUILD_COMMAND "" + BUILD_IN_SOURCE 1 + BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --features ffi-headers --target ${RUST_TARGET} INSTALL_COMMAND "") -ExternalProject_Get_Property(pseudosem SOURCE_DIR) -set(PSEUDOSEM_INCLUDE_DIRS "${SOURCE_DIR}/include") +ExternalProject_Get_Property(loot-condition-interpreter SOURCE_DIR) +set (LCI_INCLUDE_DIRS "${SOURCE_DIR}/ffi/include") +set (LCI_LIBRARIES "${SOURCE_DIR}/target/${RUST_TARGET}/release/${CMAKE_STATIC_LIBRARY_PREFIX}loot_condition_interpreter_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}") +IF (CMAKE_SYSTEM_NAME MATCHES "Windows") + set (LCI_LIBRARIES ${LCI_LIBRARIES} Userenv) +ELSE () + set (LCI_LIBRARIES ${LCI_LIBRARIES} dl) +ENDIF () ExternalProject_Add(testing-metadata PREFIX "external" @@ -235,7 +242,6 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/include/loot/vertex.h" "${CMAKE_SOURCE_DIR}/src/api/api_database.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.h" - "${CMAKE_SOURCE_DIR}/src/api/metadata/condition_grammar.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/file.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/group.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/location.h" @@ -269,7 +275,6 @@ set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/api/internals/game/game_t "${CMAKE_SOURCE_DIR}/src/tests/api/internals/helpers/version_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/helpers/yaml_set_helpers_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/condition_evaluator_test.h" - "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/condition_grammar_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/conditional_metadata_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/file_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/group_test.h" @@ -310,11 +315,11 @@ include_directories ("${CMAKE_SOURCE_DIR}/src" ${LIBLOADORDER_INCLUDE_DIRS} ${LIBGIT2_INCLUDE_DIRS} ${ESPLUGIN_INCLUDE_DIRS} + ${LCI_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${SPDLOG_INCLUDE_DIRS} ${YAML_CPP_INCLUDE_DIRS} - ${GTEST_INCLUDE_DIRS} - ${PSEUDOSEM_INCLUDE_DIRS}) + ${GTEST_INCLUDE_DIRS}) ############################## # System-Specific Settings @@ -380,13 +385,13 @@ ENDIF () # Build tests. add_executable (loot_api_internals_tests ${LOOT_API_SRC} ${LOOT_API_HEADERS} ${LOOT_TESTS_SRC} ${LOOT_TESTS_HEADERS}) -add_dependencies (loot_api_internals_tests esplugin libgit2 libloadorder pseudosem spdlog yaml-cpp GTest testing-metadata testing-plugins) -target_link_libraries(loot_api_internals_tests ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${YAML_CPP_LIBRARIES} ${GTEST_LIBRARIES}) +add_dependencies (loot_api_internals_tests esplugin libgit2 libloadorder loot-condition-interpreter spdlog yaml-cpp GTest testing-metadata testing-plugins) +target_link_libraries(loot_api_internals_tests ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${LCI_LIBRARIES} ${YAML_CPP_LIBRARIES} ${GTEST_LIBRARIES}) # Build API. add_library (loot_api ${LOOT_API_SRC} ${LOOT_API_HEADERS}) -add_dependencies (loot_api esplugin libgit2 libloadorder pseudosem spdlog yaml-cpp) -target_link_libraries(loot_api ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${YAML_CPP_LIBRARIES}) +add_dependencies (loot_api esplugin libgit2 libloadorder loot-condition-interpreter spdlog yaml-cpp) +target_link_libraries(loot_api ${Boost_LIBRARIES} ${LIBGIT2_LIBRARIES} ${ESPLUGIN_LIBRARIES} ${LIBLOADORDER_LIBRARIES} ${LOOT_LIBS} ${LCI_LIBRARIES} ${YAML_CPP_LIBRARIES}) # Build API tests. add_executable (loot_api_tests ${LOOT_API_TESTS_SRC} ${LOOT_API_TESTS_HEADERS}) diff --git a/src/api/api_database.cpp b/src/api/api_database.cpp index 1796e142..25eb6447 100644 --- a/src/api/api_database.cpp +++ b/src/api/api_database.cpp @@ -36,12 +36,8 @@ #include "loot/exception/file_access_error.h" namespace loot { -ApiDatabase::ApiDatabase(const GameType gameType, - const std::filesystem::path& dataPath, - std::shared_ptr gameCache, - std::shared_ptr loadOrderHandler) : - gameCache_(gameCache), - conditionEvaluator_(gameType, dataPath, gameCache, loadOrderHandler) {} +ApiDatabase::ApiDatabase(std::shared_ptr conditionEvaluator) : + conditionEvaluator_(conditionEvaluator) {} /////////////////////////////////// // Database Loading Functions @@ -146,10 +142,10 @@ std::vector ApiDatabase::GetGeneralMessages( if (evaluateConditions) { // Evaluate conditions from scratch. - gameCache_->ClearCachedConditions(); + conditionEvaluator_->ClearConditionCache(); for (auto it = std::begin(masterlistMessages); it != std::end(masterlistMessages);) { - if (!conditionEvaluator_.evaluate(it->GetCondition())) + if (!conditionEvaluator_->Evaluate(it->GetCondition())) it = masterlistMessages.erase(it); else ++it; @@ -224,7 +220,7 @@ std::optional ApiDatabase::GetPluginMetadata(const std::string& } if (evaluateConditions && metadata) { - return conditionEvaluator_.evaluateAll(metadata.value()); + return conditionEvaluator_->EvaluateAll(metadata.value()); } return metadata; @@ -236,7 +232,7 @@ std::optional ApiDatabase::GetPluginUserMetadata( auto metadata = userlist_.FindPlugin(plugin); if (evaluateConditions && metadata) { - return conditionEvaluator_.evaluateAll(metadata.value()); + return conditionEvaluator_->EvaluateAll(metadata.value()); } return metadata; diff --git a/src/api/api_database.h b/src/api/api_database.h index 39823361..b9ed7c3c 100644 --- a/src/api/api_database.h +++ b/src/api/api_database.h @@ -40,10 +40,7 @@ namespace loot { struct ApiDatabase : public DatabaseInterface { - ApiDatabase(const GameType gameType, - const std::filesystem::path& dataPath, - std::shared_ptr gameCache, - std::shared_ptr loadOrderHandler); + ApiDatabase(std::shared_ptr conditionEvaluator); void LoadLists(const std::filesystem::path& masterlist_path, const std::filesystem::path& userlist_path = ""); @@ -92,8 +89,7 @@ struct ApiDatabase : public DatabaseInterface { void DiscardAllUserMetadata(); private: - std::shared_ptr gameCache_; - ConditionEvaluator conditionEvaluator_; + std::shared_ptr conditionEvaluator_; Masterlist masterlist_; MetadataList userlist_; }; diff --git a/src/api/game/game.cpp b/src/api/game/game.cpp index 8927e535..3cbb2095 100644 --- a/src/api/game/game.cpp +++ b/src/api/game/game.cpp @@ -71,8 +71,9 @@ Game::Game(const GameType gameType, loadOrderHandler_->Init(type_, gamePath_, localDataPath); - database_ = std::make_shared( - Type(), DataPath(), GetCache(), GetLoadOrderHandler()); + conditionEvaluator_ = std::make_shared(Type(), DataPath()); + + database_ = std::make_shared(conditionEvaluator_); } GameType Game::Type() const { return type_; } @@ -192,6 +193,8 @@ void Game::LoadPlugins(const std::vector& plugins, if (thread.joinable()) thread.join(); } + + conditionEvaluator_->RefreshState(cache_); } std::shared_ptr Game::GetPlugin( @@ -225,6 +228,7 @@ std::vector Game::SortPlugins( void Game::LoadCurrentLoadOrderState() { loadOrderHandler_->LoadCurrentState(); + conditionEvaluator_->RefreshState(loadOrderHandler_); } bool Game::IsPluginActive(const std::string& pluginName) const { diff --git a/src/api/game/game.h b/src/api/game/game.h index 27cea10d..85710dbf 100644 --- a/src/api/game/game.h +++ b/src/api/game/game.h @@ -30,6 +30,7 @@ #include "api/game/game_cache.h" #include "api/game/load_order_handler.h" +#include "api/metadata/condition_evaluator.h" #include "loot/game_interface.h" namespace loot { @@ -80,6 +81,7 @@ private: std::shared_ptr cache_; std::shared_ptr loadOrderHandler_; + std::shared_ptr conditionEvaluator_; std::shared_ptr database_; const GameType type_; diff --git a/src/api/game/game_cache.cpp b/src/api/game/game_cache.cpp index 80c7dc86..4d7cf310 100644 --- a/src/api/game/game_cache.cpp +++ b/src/api/game/game_cache.cpp @@ -38,52 +38,16 @@ namespace loot { GameCache::GameCache() {} GameCache::GameCache(const GameCache& cache) : - conditions_(cache.conditions_), plugins_(cache.plugins_) {} GameCache& GameCache::operator=(const GameCache& cache) { if (&cache != this) { - conditions_ = cache.conditions_; plugins_ = cache.plugins_; } return *this; } -void GameCache::CacheCondition(const std::string& condition, bool result) { - lock_guard guard(mutex_); - conditions_.insert(pair(condition, result)); -} - -std::pair GameCache::GetCachedCondition( - const std::string& condition) const { - lock_guard guard(mutex_); - - auto it = conditions_.find(condition); - - if (it != conditions_.end()) - return pair(it->second, true); - else - return pair(false, false); -} - -uint32_t GameCache::GetCachedCrc(const std::string& file) const { - lock_guard guard(mutex_); - - auto it = crcs_.find(to_lower(file)); - - if (it != crcs_.end()) { - return it->second; - } - - return 0; -} - -void GameCache::CacheCrc(const std::string& file, uint32_t crc) { - lock_guard guard(mutex_); - crcs_.insert(pair(to_lower(file), crc)); -} - std::set> GameCache::GetPlugins() const { std::set> output; std::transform( @@ -131,13 +95,6 @@ void GameCache::CacheArchivePath(const std::filesystem::path& path) archivePaths_.insert(path); } -void GameCache::ClearCachedConditions() { - lock_guard guard(mutex_); - - conditions_.clear(); - crcs_.clear(); -} - void GameCache::ClearCachedPlugins() { lock_guard guard(mutex_); diff --git a/src/api/game/game_cache.h b/src/api/game/game_cache.h index 3df21ecf..6127776a 100644 --- a/src/api/game/game_cache.h +++ b/src/api/game/game_cache.h @@ -39,13 +39,6 @@ public: GameCache& operator=(const GameCache& cache); - // Returns false for second bool if no cached condition. - std::pair GetCachedCondition(const std::string& condition) const; - void CacheCondition(const std::string& condition, bool result); - - uint32_t GetCachedCrc(const std::string& file) const; - void CacheCrc(const std::string& file, uint32_t crc); - std::set> GetPlugins() const; std::shared_ptr GetPlugin(const std::string& pluginName) const; void AddPlugin(const Plugin&& plugin); @@ -53,13 +46,10 @@ public: std::set GetArchivePaths() const; void CacheArchivePath(const std::filesystem::path& path); - void ClearCachedConditions(); void ClearCachedPlugins(); void ClearCachedArchivePaths(); private: - std::unordered_map conditions_; - std::unordered_map crcs_; std::unordered_map> plugins_; std::set archivePaths_; diff --git a/src/api/game/load_order_handler.cpp b/src/api/game/load_order_handler.cpp index 03a62b29..cf64efd8 100644 --- a/src/api/game/load_order_handler.cpp +++ b/src/api/game/load_order_handler.cpp @@ -125,6 +125,26 @@ std::vector LoadOrderHandler::GetLoadOrder() const { return loadOrder; } +std::vector LoadOrderHandler::GetActivePlugins() const { + auto logger = getLogger(); + if (logger) { + logger->debug("Getting active plugins."); + } + + char** pluginArr; + size_t pluginArrSize; + + unsigned int ret = + lo_get_active_plugins(gh_, &pluginArr, &pluginArrSize); + + HandleError("get active plugins", ret); + + std::vector loadOrder(pluginArr, pluginArr + pluginArrSize); + lo_free_string_array(pluginArr, pluginArrSize); + + return loadOrder; +} + std::vector LoadOrderHandler::GetImplicitlyActivePlugins() const { auto logger = getLogger(); if (logger) { diff --git a/src/api/game/load_order_handler.h b/src/api/game/load_order_handler.h index 58f79559..ea57ccd4 100644 --- a/src/api/game/load_order_handler.h +++ b/src/api/game/load_order_handler.h @@ -48,6 +48,8 @@ public: std::vector GetLoadOrder() const; + std::vector GetActivePlugins() const; + std::vector GetImplicitlyActivePlugins() const; bool IsPluginActive(const std::string& pluginName) const; diff --git a/src/api/helpers/version.cpp b/src/api/helpers/version.cpp index 335b530d..ec261509 100644 --- a/src/api/helpers/version.cpp +++ b/src/api/helpers/version.cpp @@ -25,19 +25,8 @@ #include -#include #include -#ifdef _WIN32 -#ifndef UNICODE -#define UNICODE -#endif -#ifndef _UNICODE -#define _UNICODE -#endif -#include "windows.h" -#endif - using std::regex; namespace loot { @@ -60,7 +49,7 @@ const std::string pseudosemVersionRegex = /* There are a few different version formats that can appear in strings together, and in order to extract the correct one, they must be searched for in order of priority. */ -const std::vector Version::versionRegexes({ +const std::vector versionRegexes({ regex(dateRegex, regex::ECMAScript | regex::icase), regex(R"(version:?\s)" + pseudosemVersionRegex, regex::ECMAScript | regex::icase), @@ -73,97 +62,22 @@ const std::vector Version::versionRegexes({ regex::ECMAScript | regex::icase), }); -Version::Version() {} - -Version::Version(const std::string& ver) { +std::optional ExtractVersion(const std::string& text) { std::smatch what; for (const auto& versionRegex : versionRegexes) { - if (std::regex_search(ver, what, versionRegex)) { + if (std::regex_search(text, what, versionRegex)) { for (auto it = next(begin(what)); it != end(what); ++it) { if (it->str().empty()) continue; // Use the first non-empty sub-match. - verString_ = *it; - boost::trim(verString_); - return; + std::string version = *it; + boost::trim(version); + return version; } } } -} -Version::Version(const std::filesystem::path& file) { -#ifdef _WIN32 - DWORD dummy = 0; - DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy); - - if (size > 0) { - LPBYTE point = new BYTE[size]; - UINT uLen; - VS_FIXEDFILEINFO* info; - - GetFileVersionInfo(file.wstring().c_str(), 0, size, point); - - VerQueryValue(point, L"\\", (LPVOID*)&info, &uLen); - - DWORD dwLeftMost = HIWORD(info->dwFileVersionMS); - DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS); - DWORD dwSecondRight = HIWORD(info->dwFileVersionLS); - DWORD dwRightMost = LOWORD(info->dwFileVersionLS); - - delete[] point; - - verString_ = - std::to_string(dwLeftMost) + '.' + std::to_string(dwSecondLeft) + '.' + - std::to_string(dwSecondRight) + '.' + std::to_string(dwRightMost); - } -#else - // ensure filename has no quote characters in it to avoid command injection - // attacks - 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.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/'"; - - FILE* fp = popen(cmd.c_str(), "r"); - - // read out the version string - static const uint32_t BUFSIZE = 32; - char buf[BUFSIZE]; - if (nullptr != fgets(buf, BUFSIZE, fp)) { - verString_ = std::string(buf); - } - pclose(fp); - } -#endif -} - -std::string Version::AsString() const { return verString_; } - -bool Version::operator<(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) < 0; -} - -bool Version::operator>(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) > 0; -} - -bool Version::operator>=(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) >= 0; -} - -bool Version::operator<=(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) <= 0; -} - -bool Version::operator==(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) == 0; -} - -bool Version::operator!=(const Version& ver) const { - return pseudosem::compare(this->verString_, ver.AsString()) != 0; + return std::nullopt; } } diff --git a/src/api/helpers/version.h b/src/api/helpers/version.h index e721c5dd..2bd4f1b3 100644 --- a/src/api/helpers/version.h +++ b/src/api/helpers/version.h @@ -25,31 +25,11 @@ #ifndef LOOT_API_HELPERS_VERSION #define LOOT_API_HELPERS_VERSION -#include -#include +#include #include namespace loot { -// Version class for more robust version comparisons. -class Version { -public: - Version(); - Version(const std::string& ver); - Version(const std::filesystem::path& file); - - std::string AsString() const; - - bool operator>(const Version&) const; - bool operator<(const Version&) const; - bool operator>=(const Version&) const; - bool operator<=(const Version&) const; - bool operator==(const Version&) const; - bool operator!=(const Version&) const; - -private: - std::string verString_; - static const std::vector versionRegexes; -}; +std::optional ExtractVersion(const std::string& text); } #endif diff --git a/src/api/metadata/condition_evaluator.cpp b/src/api/metadata/condition_evaluator.cpp index d9fc9adf..c8b00e17 100644 --- a/src/api/metadata/condition_evaluator.cpp +++ b/src/api/metadata/condition_evaluator.cpp @@ -24,35 +24,83 @@ #include "api/metadata/condition_evaluator.h" +#include + #include "api/helpers/crc.h" #include "api/helpers/logging.h" -#include "api/metadata/condition_grammar.h" #include "loot/exception/condition_syntax_error.h" using std::filesystem::u8path; namespace loot { -ConditionEvaluator::ConditionEvaluator() : - gameType_(GameType::tes4), - gameCache_(nullptr), - loadOrderHandler_(nullptr) {} -ConditionEvaluator::ConditionEvaluator( - const GameType gameType, - const std::filesystem::path& dataPath, - std::shared_ptr gameCache, - std::shared_ptr loadOrderHandler) : - gameType_(gameType), - dataPath_(dataPath), - gameCache_(gameCache), - loadOrderHandler_(loadOrderHandler) {} - -bool ConditionEvaluator::evaluate(const std::string& condition) const { - if (shouldParseOnly()) { - // Still check that the syntax is valid. - parseCondition(condition); - return false; +void HandleError(const std::string operation, int returnCode) { + if (returnCode == LCI_OK) { + return; } + const char* message = nullptr; + std::string err = "Failed to " + operation + ". "; + lci_get_error_message(&message); + if (message == nullptr) { + err += "Error code: " + std::to_string(returnCode); + } + else { + err += "Details: " + std::string(message); + } + + auto logger = getLogger(); + if (logger) { + logger->error(err); + } + + throw ConditionSyntaxError(err); +} + +int mapGameType(GameType gameType) { + switch (gameType) { + case GameType::tes4: + return LCI_GAME_TES4; + case GameType::tes5: + return LCI_GAME_TES5; + case GameType::tes5se: + return LCI_GAME_TES5SE; + case GameType::tes5vr: + return LCI_GAME_TES5VR; + case GameType::fo3: + return LCI_GAME_FO3; + case GameType::fonv: + return LCI_GAME_FNV; + case GameType::fo4: + return LCI_GAME_FO4; + case GameType::fo4vr: + return LCI_GAME_FO4VR; + default: + throw std::runtime_error("Unrecognised game type encountered while mapping for condition evaluation."); + } +} + +std::string IntToHexString(const uint32_t value) { + std::stringstream stream; + stream << std::hex << value; + return stream.str(); +} + +ConditionEvaluator::ConditionEvaluator( + const GameType gameType, + const std::filesystem::path& dataPath) { + lci_state * state = nullptr; + + // This probably isn't correct for API users other than LOOT. + // But that probably doesn't matter, as the only things conditional + // on LOOT's version are LOOT-specific messages. + auto lootPath = std::filesystem::absolute("LOOT.exe"); + int result = lci_state_create(&state, mapGameType(gameType), dataPath.u8string().c_str(), lootPath.u8string().c_str()); + HandleError("create state object for condition evaluation", result); + + lciState_ = std::shared_ptr(state, lci_state_destroy); +} + +bool ConditionEvaluator::Evaluate(const std::string& condition) { if (condition.empty()) return true; @@ -61,30 +109,15 @@ bool ConditionEvaluator::evaluate(const std::string& condition) const { logger->trace("Evaluating condition: {}", condition); } - auto cachedValue = gameCache_->GetCachedCondition(condition); - if (cachedValue.second) - return cachedValue.first; + int result = lci_condition_eval(condition.c_str(), lciState_.get()); + if (result != LCI_RESULT_FALSE && result != LCI_RESULT_TRUE) { + HandleError("evaluate condition \"" + condition + "\"", result); + } - bool result = parseCondition(condition); - - gameCache_->CacheCondition(condition, result); - - return result; + return result == LCI_RESULT_TRUE; } -bool ConditionEvaluator::evaluate(const PluginCleaningData& cleaningData, - const std::string& pluginName) const { - if (shouldParseOnly() || pluginName.empty()) - return false; - - return cleaningData.GetCRC() == getCrc(pluginName); -} - -PluginMetadata ConditionEvaluator::evaluateAll( - const PluginMetadata& pluginMetadata) const { - if (shouldParseOnly()) - return pluginMetadata; - +PluginMetadata ConditionEvaluator::EvaluateAll(const PluginMetadata& pluginMetadata) { PluginMetadata evaluatedMetadata(pluginMetadata.GetName()); evaluatedMetadata.SetEnabled(pluginMetadata.IsEnabled()); evaluatedMetadata.SetLocations(pluginMetadata.GetLocations()); @@ -95,35 +128,35 @@ PluginMetadata ConditionEvaluator::evaluateAll( std::set fileSet; for (const auto& file : pluginMetadata.GetLoadAfterFiles()) { - if (evaluate(file.GetCondition())) + if (Evaluate(file.GetCondition())) fileSet.insert(file); } evaluatedMetadata.SetLoadAfterFiles(fileSet); fileSet.clear(); for (const auto& file : pluginMetadata.GetRequirements()) { - if (evaluate(file.GetCondition())) + if (Evaluate(file.GetCondition())) fileSet.insert(file); } evaluatedMetadata.SetRequirements(fileSet); fileSet.clear(); for (const auto& file : pluginMetadata.GetIncompatibilities()) { - if (evaluate(file.GetCondition())) + if (Evaluate(file.GetCondition())) fileSet.insert(file); } evaluatedMetadata.SetIncompatibilities(fileSet); std::vector messages; for (const auto& message : pluginMetadata.GetMessages()) { - if (evaluate(message.GetCondition())) + if (Evaluate(message.GetCondition())) messages.push_back(message); } evaluatedMetadata.SetMessages(messages); std::set tagSet; for (const auto& tag : pluginMetadata.GetTags()) { - if (evaluate(tag.GetCondition())) + if (Evaluate(tag.GetCondition())) tagSet.insert(tag); } evaluatedMetadata.SetTags(tagSet); @@ -131,14 +164,14 @@ PluginMetadata ConditionEvaluator::evaluateAll( if (!evaluatedMetadata.IsRegexPlugin()) { std::set infoSet; for (const auto& info : pluginMetadata.GetDirtyInfo()) { - if (evaluate(info, pluginMetadata.GetName())) + if (Evaluate(info, pluginMetadata.GetName())) infoSet.insert(info); } evaluatedMetadata.SetDirtyInfo(infoSet); infoSet.clear(); for (const auto& info : pluginMetadata.GetCleanInfo()) { - if (evaluate(info, pluginMetadata.GetName())) + if (Evaluate(info, pluginMetadata.GetName())) infoSet.insert(info); } evaluatedMetadata.SetCleanInfo(infoSet); @@ -147,328 +180,82 @@ PluginMetadata ConditionEvaluator::evaluateAll( return evaluatedMetadata; } -bool ConditionEvaluator::fileExists(const std::string& filePath) const { - validatePath(filePath); +void ConditionEvaluator::ClearConditionCache() { + int result = lci_state_clear_condition_cache(lciState_.get()); + HandleError("clear the condition cache", result); +} - if (shouldParseOnly()) - return false; +void ConditionEvaluator::RefreshState(std::shared_ptr loadOrderHandler) { + ClearConditionCache(); - if (filePath == "LOOT") - return true; - - // Try first checking the plugin cache, as most file entries are - // for plugins. - auto plugin = gameCache_->GetPlugin(filePath); - if (plugin) { - return true; + std::vector activePluginNameStrings = loadOrderHandler->GetActivePlugins(); + std::vector activePluginNames; + for (auto& pluginName : activePluginNameStrings) { + activePluginNames.push_back(pluginName.c_str()); } - // Not a loaded plugin, check the filesystem. - if (hasPluginFileExtension(filePath, gameType_)) - return std::filesystem::exists(dataPath_ / u8path(filePath)) || - std::filesystem::exists(dataPath_ / u8path(filePath + ".ghost")); - else - return std::filesystem::exists(dataPath_ / u8path(filePath)); + int result = lci_state_set_active_plugins(lciState_.get(), + &activePluginNames[0], + activePluginNames.size()); + HandleError("cache active plugins for condition evaluation", result); } -bool ConditionEvaluator::regexMatchExists( - const std::string& regexString) const { - auto pathRegex = splitRegex(regexString); +void ConditionEvaluator::RefreshState(std::shared_ptr gameCache) { + ClearConditionCache(); - if (shouldParseOnly()) - return false; + std::vector pluginNames; + std::vector pluginVersionStrings; + std::vector crcs; + for (auto plugin : gameCache->GetPlugins()) { + pluginNames.push_back(plugin->GetName()); + pluginVersionStrings.push_back(plugin->GetVersion().value_or("")); + crcs.push_back(plugin->GetCRC().value_or(0)); + } - return isRegexMatchInDataDirectory(pathRegex, - [](const std::string&) { return true; }); + std::vector pluginVersions; + std::vector pluginCrcs; + for (size_t i = 0; i < pluginNames.size(); ++i) { + if (!pluginVersionStrings[i].empty()) { + plugin_version pluginVersion; + pluginVersion.plugin_name = pluginNames[i].c_str(); + pluginVersion.version = pluginVersionStrings[i].c_str(); + pluginVersions.push_back(pluginVersion); + } + + if (crcs[i] != 0) { + plugin_crc pluginCrc; + pluginCrc.plugin_name = pluginNames[i].c_str(); + pluginCrc.crc = crcs[i]; + pluginCrcs.push_back(pluginCrc); + } + } + + int result = lci_state_set_plugin_versions(lciState_.get(), + &pluginVersions[0], + pluginVersions.size()); + HandleError("cache plugin versions for condition evaluation", result); + + result = lci_state_set_crc_cache(lciState_.get(), + &pluginCrcs[0], + pluginCrcs.size()); + HandleError("fill CRC cache for condition evaluation", result); } -bool ConditionEvaluator::regexMatchesExist( - const std::string& regexString) const { - auto pathRegex = splitRegex(regexString); - - if (shouldParseOnly()) +bool ConditionEvaluator::Evaluate(const PluginCleaningData& cleaningData, + const std::string& pluginName) { + if (pluginName.empty()) return false; - return areRegexMatchesInDataDirectory( - pathRegex, [](const std::string&) { return true; }); + return Evaluate("checksum(\"" + pluginName + "\", " + IntToHexString(cleaningData.GetCRC()) + ")"); } -bool ConditionEvaluator::isPluginActive(const std::string& pluginName) const { - validatePath(pluginName); - - if (shouldParseOnly()) - return false; - - if (pluginName == "LOOT") - return false; - - return loadOrderHandler_->IsPluginActive(pluginName); -} - -bool ConditionEvaluator::isPluginMatchingRegexActive( - const std::string& regexString) const { - auto pathRegex = splitRegex(regexString); - - if (shouldParseOnly()) - return false; - - return isRegexMatchInDataDirectory( - pathRegex, [&](const std::string& filename) { - return loadOrderHandler_->IsPluginActive(filename); - }); -} - -bool ConditionEvaluator::arePluginsActive( - const std::string& regexString) const { - auto pathRegex = splitRegex(regexString); - - if (shouldParseOnly()) - return false; - - return areRegexMatchesInDataDirectory( - pathRegex, [&](const std::string& filename) { - return loadOrderHandler_->IsPluginActive(filename); - }); -} - -bool ConditionEvaluator::checksumMatches(const std::string& filePath, - const uint32_t checksum) const { - validatePath(filePath); - - if (shouldParseOnly()) - return false; - - return checksum == getCrc(filePath); -} - -bool ConditionEvaluator::compareVersions(const std::string& filePath, - const std::string& testVersion, - const std::string& comparator) const { - if (!fileExists(filePath)) - return comparator == "!=" || comparator == "<" || comparator == "<="; - - Version givenVersion = Version(testVersion); - Version trueVersion = getVersion(filePath); - +void ParseCondition(const std::string& condition) { auto logger = getLogger(); if (logger) { - logger->trace("Version extracted: {}", trueVersion.AsString()); + logger->trace("Testing condition syntax: {}", condition); } - return ((comparator == "==" && trueVersion == givenVersion) || - (comparator == "!=" && trueVersion != givenVersion) || - (comparator == "<" && trueVersion < givenVersion) || - (comparator == ">" && trueVersion > givenVersion) || - (comparator == "<=" && trueVersion <= givenVersion) || - (comparator == ">=" && trueVersion >= givenVersion)); -} - -void ConditionEvaluator::validatePath(const std::filesystem::path& path) { - auto logger = getLogger(); - if (logger) { - logger->trace("Checking to see if the path \"{}\" is safe.", path.u8string()); - } - - std::filesystem::path temp; - for (const auto& component : path) { - if (component == ".") - continue; - - if (component == ".." && temp.filename() == "..") { - throw ConditionSyntaxError("Invalid file path: " + path.u8string()); - } - - temp /= component; - } -} -void ConditionEvaluator::validateRegex(const std::string& regexString) { - try { - std::regex(regexString, std::regex::ECMAScript | std::regex::icase); - } catch (std::regex_error& e) { - throw ConditionSyntaxError("Invalid regex string \"" + regexString + - "\": " + e.what()); - } -} - -std::filesystem::path ConditionEvaluator::getRegexParentPath( - const std::string& regexString) { - size_t pos = regexString.rfind('/'); - - if (pos == std::string::npos) - return std::filesystem::path(); - - return u8path(regexString.substr(0, pos)); -} - -std::string ConditionEvaluator::getRegexFilename( - const std::string& regexString) { - size_t pos = regexString.rfind('/'); - - if (pos == std::string::npos) - return regexString; - - return regexString.substr(pos + 1); -} - -std::pair ConditionEvaluator::splitRegex( - const std::string& regexString) { - // Can't support a regex string where all path components may be regex, since - // this could lead to massive scanning if an unfortunately-named directory is - // encountered. As such, only the filename portion can be a regex. Need to - // separate that from the rest of the string. - - validateRegex(regexString); - - std::string filename = getRegexFilename(regexString); - std::filesystem::path parent = getRegexParentPath(regexString); - - validatePath(parent); - - std::regex reg; - try { - reg = std::regex(filename, std::regex::ECMAScript | std::regex::icase); - } catch (std::regex_error& e) { - throw ConditionSyntaxError("Invalid regex string \"" + filename + - "\": " + e.what()); - } - - return std::pair(parent, reg); -} - -bool ConditionEvaluator::isGameSubdirectory( - const std::filesystem::path& path) const { - std::filesystem::path parentPath = dataPath_ / path; - - return std::filesystem::exists(parentPath) && - std::filesystem::is_directory(parentPath); -} - -bool ConditionEvaluator::isRegexMatchInDataDirectory( - const std::pair& pathRegex, - const std::function condition) const { - // Now we have a valid parent path and a regex filename. Check that the - // parent path exists and is a directory. - if (!isGameSubdirectory(pathRegex.first)) { - auto logger = getLogger(); - if (logger) { - logger->trace("The path \"{}\" is not a game subdirectory.", - pathRegex.first.u8string()); - } - return false; - } - - return std::any_of( - std::filesystem::directory_iterator(dataPath_ / pathRegex.first), - std::filesystem::directory_iterator(), - [&](const std::filesystem::directory_entry& entry) { - const std::string filename = entry.path().filename().u8string(); - return std::regex_match(filename, pathRegex.second) && - condition(filename); - }); -} - -bool ConditionEvaluator::areRegexMatchesInDataDirectory( - const std::pair& pathRegex, - const std::function condition) const { - bool foundOneFile = false; - - return isRegexMatchInDataDirectory(pathRegex, - [&](const std::string& filename) { - if (condition(filename)) { - if (foundOneFile) - return true; - - foundOneFile = true; - } - - return false; - }); -} -bool ConditionEvaluator::parseCondition(const std::string& condition) const { - if (condition.empty()) - return true; - - ConditionGrammar - grammar(*this); - boost::spirit::qi::space_type skipper; - std::string::const_iterator begin = condition.begin(); - std::string::const_iterator end = condition.end(); - bool evaluation; - - bool parseResult = - boost::spirit::qi::phrase_parse(begin, end, grammar, skipper, evaluation); - - if (!parseResult || begin != end) { - throw ConditionSyntaxError("Failed to parse condition \"" + condition + - "\": only partially matched expected syntax."); - } - - return evaluation; -} -Version ConditionEvaluator::getVersion(const std::string& filePath) const { - if (filePath == "LOOT") - return Version(std::filesystem::absolute("LOOT.exe")); - else { - // If the file is a plugin, its version needs to be extracted - // from its description field. Try getting an entry from the - // plugin cache. - - auto plugin = gameCache_->GetPlugin(filePath); - if (plugin) { - return Version(plugin->GetVersion().value_or("")); - } - - // The file wasn't in the plugin cache, load it as a plugin - // if it appears to be valid, otherwise treat it as a non - // plugin file. - auto pluginPath = dataPath_ / u8path(filePath); - if (Plugin::IsValid(gameType_, pluginPath)) - return Version( - Plugin(gameType_, gameCache_, pluginPath, true) - .GetVersion() - .value_or("")); - - return Version(pluginPath); - } -} -bool ConditionEvaluator::shouldParseOnly() const { - return gameCache_ == nullptr || loadOrderHandler_ == nullptr; -} - -uint32_t ConditionEvaluator::getCrc(const std::string & file) const { - uint32_t crc = gameCache_->GetCachedCrc(file); - - if (crc != 0) { - return crc; - } - - if (file == "LOOT") { - crc = GetCrc32(std::filesystem::absolute("LOOT.exe")); - gameCache_->CacheCrc(file, crc); - return crc; - } - - // Get the CRC from the game plugin cache if possible. - auto plugin = gameCache_->GetPlugin(file); - if (plugin) { - crc = plugin->GetCRC().value_or(0); - } - - // Otherwise calculate it from the file. - if (crc == 0) { - if (std::filesystem::exists(dataPath_ / u8path(file))) { - crc = GetCrc32(dataPath_ / u8path(file)); - } - else if (hasPluginFileExtension(file, gameType_) && - std::filesystem::exists(dataPath_ / u8path(file + ".ghost"))) { - crc = GetCrc32(dataPath_ / u8path(file + ".ghost")); - } - } - - if (crc != 0) { - gameCache_->CacheCrc(file, crc); - } - - return crc; + int result = lci_condition_parse(condition.c_str()); + HandleError("parse condition \"" + condition + "\"", result); } } diff --git a/src/api/metadata/condition_evaluator.h b/src/api/metadata/condition_evaluator.h index 945d3443..a73c50b1 100644 --- a/src/api/metadata/condition_evaluator.h +++ b/src/api/metadata/condition_evaluator.h @@ -26,78 +26,35 @@ #define LOOT_API_METADATA_CONDITION_EVALUATOR #include -#include #include +#include + #include "api/game/game_cache.h" #include "api/game/load_order_handler.h" -#include "api/helpers/version.h" #include "loot/metadata/plugin_cleaning_data.h" #include "loot/metadata/plugin_metadata.h" namespace loot { class ConditionEvaluator { public: - ConditionEvaluator(); ConditionEvaluator(const GameType gameType, - const std::filesystem::path& dataPath, - std::shared_ptr gameCache, - std::shared_ptr loadOrderHandler); + const std::filesystem::path& dataPath); - bool evaluate(const std::string& condition) const; - bool evaluate(const PluginCleaningData& cleaningData, - const std::string& pluginName) const; - PluginMetadata evaluateAll(const PluginMetadata& pluginMetadata) const; - - bool fileExists(const std::string& filePath) const; - bool regexMatchExists(const std::string& regexString) const; - bool regexMatchesExist(const std::string& regexString) const; - - bool isPluginActive(const std::string& pluginName) const; - bool isPluginMatchingRegexActive(const std::string& regexString) const; - bool arePluginsActive(const std::string& regexString) const; - - bool checksumMatches(const std::string& filePath, - const uint32_t checksum) const; - - bool compareVersions(const std::string& filePath, - const std::string& testVersion, - const std::string& comparator) const; + bool Evaluate(const std::string& condition); + PluginMetadata EvaluateAll(const PluginMetadata& pluginMetadata); + void ClearConditionCache(); + void RefreshState(std::shared_ptr loadOrderHandler); + void RefreshState(std::shared_ptr gameCache); private: - static void validatePath(const std::filesystem::path& path); - static void validateRegex(const std::string& regexString); + bool Evaluate(const PluginCleaningData& cleaningData, + const std::string& pluginName); - static std::filesystem::path getRegexParentPath( - const std::string& regexString); - static std::string getRegexFilename(const std::string& regexString); - - // Split a regex string into the non-regex filesystem parent path, and the - // regex filename. - static std::pair splitRegex( - const std::string& regexString); - - bool isGameSubdirectory(const std::filesystem::path& path) const; - bool isRegexMatchInDataDirectory( - const std::pair& pathRegex, - const std::function condition) const; - bool areRegexMatchesInDataDirectory( - const std::pair& pathRegex, - const std::function condition) const; - - bool parseCondition(const std::string& condition) const; - - Version getVersion(const std::string& filePath) const; - - bool shouldParseOnly() const; - - uint32_t getCrc(const std::string& file) const; - - const GameType gameType_; - const std::filesystem::path dataPath_; - const std::shared_ptr gameCache_; - const std::shared_ptr loadOrderHandler_; + std::shared_ptr lciState_; }; + +void ParseCondition(const std::string& condition); } #endif diff --git a/src/api/metadata/condition_grammar.h b/src/api/metadata/condition_grammar.h deleted file mode 100644 index 05b6224d..00000000 --- a/src/api/metadata/condition_grammar.h +++ /dev/null @@ -1,304 +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 - . - */ - -#ifndef LOOT_API_METADATA_CONDITION_GRAMMAR -#define LOOT_API_METADATA_CONDITION_GRAMMAR - -#ifndef BOOST_SPIRIT_UNICODE -#define BOOST_SPIRIT_UNICODE -#endif - -#ifndef BOOST_SPIRIT_USE_PHOENIX_V3 -#define BOOST_SPIRIT_USE_PHOENIX_V3 1 -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "api/game/game.h" -#include "api/helpers/logging.h" -#include "api/helpers/version.h" -#include "api/metadata/condition_evaluator.h" -#include "api/plugin.h" -#include "loot/exception/condition_syntax_error.h" - -namespace loot { -template -class ConditionGrammar - : public boost::spirit::qi::grammar { -public: - ConditionGrammar(const ConditionEvaluator& evaluator) : - ConditionGrammar::base_type(expression_, "condition grammar"), - evaluator_(evaluator) { - using boost::spirit::unicode::char_; - using boost::spirit::unicode::string; - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - expression_ = - qi::eps > compound_[qi::labels::_val = qi::labels::_1] >> - *((qi::lit("or") >> - compound_)[qi::labels::_val = qi::labels::_val || qi::labels::_1]); - - compound_ = - condition_[qi::labels::_val = qi::labels::_1] >> - *((qi::lit("and") >> - condition_)[qi::labels::_val = qi::labels::_val && qi::labels::_1]); - - condition_ = - function_[qi::labels::_val = qi::labels::_1] | - (qi::lit("not") > condition_)[qi::labels::_val = !qi::labels::_1] | - ('(' > expression_ > ')')[qi::labels::_val = qi::labels::_1]; - - function_ = - ("file(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckFile, - this, - qi::labels::_val, - qi::labels::_1)] | - ("many(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckMany, - this, - qi::labels::_val, - qi::labels::_1)] | - ("checksum(" > filePath_ > ',' > qi::hex > - ')')[phoenix::bind(&ConditionGrammar::CheckSum, - this, - qi::labels::_val, - qi::labels::_1, - qi::labels::_2)] | - ("version(" > filePath_ > ',' > quotedStr_ > ',' > comparator_ > - ')')[phoenix::bind(&ConditionGrammar::CheckVersion, - this, - qi::labels::_val, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3)] | - ("active(" > quotedStr_ > - ')')[phoenix::bind(&ConditionGrammar::CheckActive, - this, - qi::labels::_val, - qi::labels::_1)] | - ("many_active(" > quotedStr_ > - ')')[phoenix::bind(&ConditionGrammar::CheckManyActive, - this, - qi::labels::_val, - qi::labels::_1)]; - - quotedStr_ %= '"' > +(char_ - '"') > '"'; - - filePath_ %= '"' > +(char_ - invalidPathChars_) > '"'; - - invalidPathChars_ %= char_(':') | char_('*') | char_('?') | char_('"') | - char_('<') | char_('>') | char_('|'); - - comparator_ %= string("==") | string("!=") | string("<=") | string(">=") | - string("<") | string(">"); - - expression_.name("expression"); - compound_.name("compound condition"); - condition_.name("condition"); - function_.name("function"); - quotedStr_.name("quoted string"); - filePath_.name("file path"); - comparator_.name("comparator"); - invalidPathChars_.name("invalid file path characters"); - - qi::on_error(expression_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(compound_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(condition_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(function_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(quotedStr_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(filePath_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(comparator_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - qi::on_error(invalidPathChars_, - phoenix::bind(&ConditionGrammar::SyntaxError, - this, - qi::labels::_1, - qi::labels::_2, - qi::labels::_3, - qi::labels::_4)); - - logger_ = getLogger(); - } - -private: - bool IsRegex(const std::string& file) const { - // Treat as regex if the plugin filename contains any of ":\*?|" as - // they are not valid Windows filename characters, but have meaning - // in regexes. - return strpbrk(file.c_str(), ":\\*?|") != nullptr; - } - - // Eval's exact paths. Check for files and ghosted plugins. - void CheckFile(bool& result, const std::string& file) const { - if (logger_) { - logger_->trace("Checking to see if the file \"{}\" exists.", file); - } - - result = false; - if (IsRegex(file)) - result = evaluator_.regexMatchExists(file); - else - result = evaluator_.fileExists(file); - - if (logger_) { - logger_->trace("File check result: {}", result); - } - } - - void CheckMany(bool& result, const std::string& regexStr) const { - if (logger_) { - logger_->trace( - "Checking to see if more than one file matching the regex \"{}\" " - "exists.", - regexStr); - } - - result = false; - result = evaluator_.regexMatchesExist(regexStr); - } - - void CheckSum(bool& result, - const std::string& file, - const uint32_t checksum) { - if (logger_) { - logger_->trace("Checking the CRC of the file \"{}\".", file); - } - - result = false; - result = evaluator_.checksumMatches(file, checksum); - } - - void CheckVersion(bool& result, - const std::string& file, - const std::string& version, - const std::string& comparator) const { - if (logger_) { - logger_->trace("Checking the version of the file \"{}\".", file); - } - - result = false; - result = evaluator_.compareVersions(file, version, comparator); - - if (logger_) { - logger_->trace("Version check result: {}", result); - } - } - - void CheckActive(bool& result, const std::string& file) const { - result = false; - if (IsRegex(file)) - result = evaluator_.isPluginMatchingRegexActive(file); - else - result = evaluator_.isPluginActive(file); - - if (logger_) { - logger_->trace("Active check result: {}", result); - } - } - - void CheckManyActive(bool& result, const std::string& regexStr) const { - if (logger_) { - logger_->trace( - "Checking to see if more than one file matching the regex \"{}\" is " - "active.", - regexStr); - } - - result = false; - result = evaluator_.arePluginsActive(regexStr); - } - - void SyntaxError(Iterator const& first, - Iterator const& last, - Iterator const& errorpos, - boost::spirit::info const& what) { - std::string condition(first, last); - std::string context(errorpos, last); - boost::trim(context); - - throw ConditionSyntaxError("Failed to parse condition \"" + condition + - "\": expected \"" + what.tag + "\" at \"" + - context + "\"."); - } - - boost::spirit::qi::rule expression_, compound_, - condition_, function_; - boost::spirit::qi::rule quotedStr_, filePath_, - comparator_; - boost::spirit::qi::rule invalidPathChars_; - - const ConditionEvaluator& evaluator_; - std::shared_ptr logger_; -}; -} -#endif diff --git a/src/api/metadata/conditional_metadata.cpp b/src/api/metadata/conditional_metadata.cpp index dc52f1b1..69f2929e 100644 --- a/src/api/metadata/conditional_metadata.cpp +++ b/src/api/metadata/conditional_metadata.cpp @@ -42,11 +42,7 @@ std::string ConditionalMetadata::GetCondition() const { return condition_; } void ConditionalMetadata::ParseCondition() const { if (!condition_.empty()) { - auto logger = getLogger(); - if (logger) { - logger->trace("Testing condition syntax: {}", condition_); - } - ConditionEvaluator().evaluate(condition_); + loot::ParseCondition(condition_); } } } diff --git a/src/api/metadata_list.cpp b/src/api/metadata_list.cpp index e2bdfdac..1d6ec622 100644 --- a/src/api/metadata_list.cpp +++ b/src/api/metadata_list.cpp @@ -195,14 +195,14 @@ void MetadataList::AppendMessage(const Message& message) { } void MetadataList::EvalAllConditions( - const ConditionEvaluator& conditionEvaluator) { + ConditionEvaluator& conditionEvaluator) { if (unevaluatedPlugins_.empty()) unevaluatedPlugins_.swap(plugins_); else plugins_.clear(); for (const auto& plugin : unevaluatedPlugins_) { - plugins_.insert(conditionEvaluator.evaluateAll(plugin)); + plugins_.insert(conditionEvaluator.EvaluateAll(plugin)); } if (unevaluatedRegexPlugins_.empty()) @@ -211,7 +211,7 @@ void MetadataList::EvalAllConditions( regexPlugins_ = unevaluatedRegexPlugins_; for (auto& plugin : regexPlugins_) { - plugin = conditionEvaluator.evaluateAll(plugin); + plugin = conditionEvaluator.EvaluateAll(plugin); } if (unevaluatedMessages_.empty()) @@ -220,7 +220,7 @@ void MetadataList::EvalAllConditions( messages_.clear(); for (const auto& message : unevaluatedMessages_) { - if (conditionEvaluator.evaluate(message.GetCondition())) + if (conditionEvaluator.Evaluate(message.GetCondition())) messages_.push_back(message); } } diff --git a/src/api/metadata_list.h b/src/api/metadata_list.h index 3aa86948..a415adbb 100644 --- a/src/api/metadata_list.h +++ b/src/api/metadata_list.h @@ -60,7 +60,7 @@ public: void AppendMessage(const Message& message); // Eval plugin conditions. - void EvalAllConditions(const ConditionEvaluator& conditionEvaluator); + void EvalAllConditions(ConditionEvaluator& conditionEvaluator); protected: std::unordered_set groups_; diff --git a/src/api/plugin.cpp b/src/api/plugin.cpp index 0145aa00..eee2c24c 100644 --- a/src/api/plugin.cpp +++ b/src/api/plugin.cpp @@ -139,12 +139,7 @@ float Plugin::GetHeaderVersion() const { } std::optional Plugin::GetVersion() const { - std::string version = Version(GetDescription()).AsString(); - if (version.empty()) { - return std::nullopt; - } - - return version; + return ExtractVersion(GetDescription()); } std::vector Plugin::GetMasters() const { diff --git a/src/tests/api/internals/game/game_cache_test.h b/src/tests/api/internals/game/game_cache_test.h index 99d45fa6..74b5260a 100644 --- a/src/tests/api/internals/game/game_cache_test.h +++ b/src/tests/api/internals/game/game_cache_test.h @@ -52,40 +52,6 @@ protected: // all. INSTANTIATE_TEST_CASE_P(, GameCacheTest, ::testing::Values(GameType::tes5)); -TEST_P(GameCacheTest, gettingATrueConditionShouldReturnATrueTruePair) { - EXPECT_NO_THROW(cache_.CacheCondition(condition, true)); - - EXPECT_EQ(std::make_pair(true, true), - cache_.GetCachedCondition(condition)); -} - -TEST_P(GameCacheTest, gettingAFalseConditionShouldReturnAFalseTruePair) { - EXPECT_NO_THROW(cache_.CacheCondition(condition, false)); - - EXPECT_EQ(std::make_pair(false, true), - cache_.GetCachedCondition(condition)); -} - -TEST_P(GameCacheTest, gettingACachedConditionShouldBeCaseSensitive) { - EXPECT_NO_THROW(cache_.CacheCondition(condition, false)); - - EXPECT_EQ(std::make_pair(false, false), - cache_.GetCachedCondition(conditionLowercase)); -} - -TEST_P(GameCacheTest, gettingANonCachedConditionShouldReturnAFalseFalsePair) { - EXPECT_EQ(std::make_pair(false, false), cache_.GetCachedCondition(condition)); -} - -TEST_P(GameCacheTest, gettingACachedCrcShouldReturnTheValue) { - cache_.CacheCrc(boost::locale::to_upper(blankEsm), 5); - EXPECT_EQ(5, cache_.GetCachedCrc(blankEsm)); -} - -TEST_P(GameCacheTest, gettingAnUncachedCrcShouldReturnZero) { - EXPECT_EQ(0, cache_.GetCachedCrc(blankEsm)); -} - TEST_P(GameCacheTest, addingAPluginThatDoesNotExistShouldSucceed) { cache_.AddPlugin(Plugin(game_.Type(), std::make_shared(GameCache()), @@ -158,22 +124,6 @@ TEST_P(GameCacheTest, EXPECT_EQ(expected, cache_.GetArchivePaths()); } -TEST_P(GameCacheTest, - clearingCachedConditionsShouldNotThrowIfNoConditionsAreCached) { - EXPECT_NO_THROW(cache_.ClearCachedConditions()); -} - -TEST_P(GameCacheTest, clearingCachedConditionsShouldClearAnyCachedConditionsAndCrcs) { - EXPECT_NO_THROW(cache_.CacheCondition(condition, true)); - cache_.CacheCrc(blankEsm, 5); - - EXPECT_NO_THROW(cache_.ClearCachedConditions()); - - EXPECT_EQ(std::make_pair(false, false), - cache_.GetCachedCondition(conditionLowercase)); - EXPECT_EQ(0, cache_.GetCachedCrc(blankEsm)); -} - TEST_P(GameCacheTest, clearingCachedPluginsShouldNotThrowIfNoPluginsAreCached) { EXPECT_NO_THROW(cache_.ClearCachedPlugins()); } diff --git a/src/tests/api/internals/game/load_order_handler_test.h b/src/tests/api/internals/game/load_order_handler_test.h index c2b09dda..38322573 100644 --- a/src/tests/api/internals/game/load_order_handler_test.h +++ b/src/tests/api/internals/game/load_order_handler_test.h @@ -90,6 +90,16 @@ protected: } } + std::vector getActivePlugins() { + std::vector activePlugins; + for (auto& pair : getInitialLoadOrder()) { + if (pair.second) { + activePlugins.push_back(pair.first); + } + } + return activePlugins; + } + LoadOrderHandler loadOrderHandler_; std::vector loadOrderToSet_; }; @@ -170,6 +180,28 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) { ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder()); } +TEST_P(LoadOrderHandlerTest, + getActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) { + EXPECT_THROW(loadOrderHandler_.GetActivePlugins(), + std::system_error); +} + +TEST_P(LoadOrderHandlerTest, + getActivePluginsShouldReturnAnEmptyVectorIfStateHasNotBeenLoaded) { + initialiseHandler(); + + EXPECT_TRUE(loadOrderHandler_.GetActivePlugins().empty()); +} +TEST_P( + LoadOrderHandlerTest, + getActivePluginsShouldReturnOnlyActivePlugins) { + initialiseHandler(); + loadOrderHandler_.LoadCurrentState(); + + ASSERT_EQ(getActivePlugins(), + loadOrderHandler_.GetActivePlugins()); +} + TEST_P(LoadOrderHandlerTest, getImplicitlyActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) { EXPECT_THROW(loadOrderHandler_.GetImplicitlyActivePlugins(), diff --git a/src/tests/api/internals/helpers/version_test.h b/src/tests/api/internals/helpers/version_test.h index 9a8bc994..f058def1 100644 --- a/src/tests/api/internals/helpers/version_test.h +++ b/src/tests/api/internals/helpers/version_test.h @@ -32,264 +32,115 @@ 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::GetVersionString() + ".0"); - EXPECT_EQ(expected, version.AsString()); -} - -TEST(Version, shouldExtractVersionFromApiDll) { - // Use the API DLL built. - Version version(std::filesystem::path("loot_api.dll")); - std::string expected(LootVersion::GetVersionString() + ".0"); - EXPECT_EQ(expected, version.AsString()); -} -#endif - -TEST(Version, defaultConstructorShouldSetEmptyVersionString) { - EXPECT_EQ("", Version().AsString()); -} TEST(Version, shouldExtractAVersionContainingASingleDigit) { - Version version(std::string("5")); - EXPECT_EQ("5", version.AsString()); + EXPECT_EQ("5", ExtractVersion("5").value()); } TEST(Version, shouldExtractAVersionContainingMultipleDigits) { - Version version(std::string("10")); - EXPECT_EQ("10", version.AsString()); + EXPECT_EQ("10", ExtractVersion("10").value()); } TEST(Version, shouldExtractAVersionContainingMultipleNumbers) { - Version version(std::string("10.11.12.13")); - EXPECT_EQ("10.11.12.13", version.AsString()); + + EXPECT_EQ("10.11.12.13", ExtractVersion("10.11.12.13").value()); } TEST(Version, shouldExtractASemanticVersion) { - Version version(std::string("1.0.0-x.7.z.92+exp.sha.5114f85")); - EXPECT_EQ("1.0.0-x.7.z.92", version.AsString()); + EXPECT_EQ("1.0.0-x.7.z.92", ExtractVersion("1.0.0-x.7.z.92+exp.sha.5114f85").value()); } TEST(Version, shouldExtractAPseudosemExtendedVersionStoppingAtTheFirstSpaceSeparator) { - Version version(std::string("01.0.0_alpha:1-2 3")); - EXPECT_EQ("01.0.0_alpha:1-2", version.AsString()); + EXPECT_EQ("01.0.0_alpha:1-2", ExtractVersion("01.0.0_alpha:1-2 3").value()); } TEST(Version, shouldExtractAVersionSubstring) { - Version version(std::string("v5.0")); - EXPECT_EQ("5.0", version.AsString()); + EXPECT_EQ("5.0", ExtractVersion("v5.0").value()); } TEST(Version, shouldBeEmptyIfInputStringContainedNoVersion) { - Version version(std::string("The quick brown fox jumped over the lazy dog.")); - EXPECT_EQ("", version.AsString()); + EXPECT_FALSE(ExtractVersion("The quick brown fox jumped over the lazy dog.").has_value()); } TEST(Version, shouldExtractTimestampWithForwardslashDateSeparators) { // Found in a Bashed Patch. Though the timestamp isn't useful to // LOOT, it is semantically a version, and extracting it is far // easier than trying to skip it and the number of records changed. - Version version( - std::string("Updated: 10/09/2016 13:15:18\r\n\r\nRecords Changed: 43")); - EXPECT_EQ("10/09/2016 13:15:18", version.AsString()); + auto text = ExtractVersion("Updated: 10/09/2016 13:15:18\r\n\r\nRecords Changed: 43"); + EXPECT_EQ("10/09/2016 13:15:18", text.value()); } TEST(Version, shouldNotExtractTrailingPeriods) { // Found in . - Version version(std::string("Version 0.2.")); - EXPECT_EQ("0.2", version.AsString()); + EXPECT_EQ("0.2", ExtractVersion("Version 0.2.").value()); } TEST(Version, shouldExtractVersionAfterTextWhenPrecededByVersionColonString) { // Found in . - std::string testText("Legendary Edition\r\n\r\nVersion: 3.0.0"); - EXPECT_EQ("3.0.0", Version(testText).AsString()); + EXPECT_EQ("3.0.0", ExtractVersion("Legendary Edition\r\n\r\nVersion: 3.0.0").value()); } TEST(Version, shouldIgnoreNumbersContainingCommas) { // Found in . - std::string testText("fixing over 2,300 bugs so far! Version: 3.5.3"); - EXPECT_EQ("3.5.3", Version(testText).AsString()); + EXPECT_EQ("3.5.3", ExtractVersion("fixing over 2,300 bugs so far! Version: 3.5.3").value()); } TEST(Version, shouldExtractVersionBeforeText) { // Found in . - std::string testText("Version: 2.1 The Unofficial Fallout 3 Patch"); - EXPECT_EQ("2.1", Version(testText).AsString()); + EXPECT_EQ("2.1", ExtractVersion("Version: 2.1 The Unofficial Fallout 3 Patch").value()); } TEST(Version, shouldExtractVersionWithPrecedingV) { // Found in . - std::string testText("V2.11\r\n\r\n{{BASH:Invent}}"); - EXPECT_EQ("2.11", Version(testText).AsString()); + EXPECT_EQ("2.11", ExtractVersion("V2.11\r\n\r\n{{BASH:Invent}}").value()); } TEST(Version, shouldExtractVersionWithPrecedingColonPeriodWhitespace) { // Found in . - std::string testText("Version:. 1.09"); - EXPECT_EQ("1.09", Version(testText).AsString()); + EXPECT_EQ("1.09", ExtractVersion("Version:. 1.09").value()); } TEST(Version, shouldExtractVersionWithLettersImmediatelyAfterNumbers) { // Found in . - std::string testText( - "comprehensive bugfixing mod for The Elder Scrolls V: " - "Skyrim\r\n\r\nVersion: 2.1.3b\r\n\r\n"); - EXPECT_EQ("2.1.3b", Version(testText).AsString()); + auto text = ExtractVersion( + "comprehensive bugfixing mod for The Elder Scrolls V: " + "Skyrim\r\n\r\nVersion: 2.1.3b\r\n\r\n"); + EXPECT_EQ("2.1.3b", text.value()); } TEST(Version, shouldExtractVersionWithPeriodAndNoPrecedingIdentifier) { // Found in . - std::string testText("SkyUI 5.1"); - EXPECT_EQ("5.1", Version(testText).AsString()); + EXPECT_EQ("5.1", ExtractVersion("SkyUI 5.1").value()); } TEST(Version, shouldNotExtractSingleDigitInSentence) { // Found in . - std::string testText( + auto text = ExtractVersion( "Adds 8 variants of Triss Merigold's outfit from \"The Witcher 2\""); - EXPECT_EQ("", Version(testText).AsString()); + EXPECT_FALSE(text.has_value()); } TEST(Version, shouldPreferVersionPrefixedNumbersOverVersionsInSentence) { // Found in - std::string testText( + auto text = ExtractVersion( "Requires Skyrim patch 1.9.32.0.8 or greater.\n" "Requires Unofficial Skyrim Legendary Edition Patch 3.0.0 or greater.\n" "Version 2.0.0"); - EXPECT_EQ("2.0.0", Version(testText).AsString()); + EXPECT_EQ("2.0.0", text.value()); } TEST(Version, shouldExtractSingleDigitVersionPrecededByV) { // Found in - std::string testText("Immersive Armors v8 Main Plugin"); - EXPECT_EQ("8", Version(testText).AsString()); + EXPECT_EQ("8", ExtractVersion("Immersive Armors v8 Main Plugin").value()); } TEST(Version, shouldPreferVersionPrefixedNumbersOverVPrefixedNumber) { // Found in - std::string testText( + auto text = ExtractVersion( "Compatibility patch for AOS v2.5 and True Storms v1.5 (or " "later),\nPatch Version: 1.0"); - EXPECT_EQ("1.0", Version(testText).AsString()); -} - -TEST(Version, GreaterThan) { - Version version1, version2; - EXPECT_FALSE(version1 > version2); - EXPECT_FALSE(version2 > version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 > version2); - EXPECT_FALSE(version2 > version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 > version2); - EXPECT_TRUE(version2 > version1); -} - -TEST(Version, LessThan) { - Version version1, version2; - EXPECT_FALSE(version1 < version2); - EXPECT_FALSE(version2 < version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 < version2); - EXPECT_FALSE(version2 < version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 < version2); - EXPECT_FALSE(version2 < version1); -} - -TEST(Version, GreaterThanEqual) { - Version version1, version2; - EXPECT_TRUE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); -} - -TEST(Version, LessThanEqual) { - Version version1, version2; - EXPECT_TRUE(version1 <= version2); - EXPECT_TRUE(version2 <= version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 <= version2); - EXPECT_TRUE(version2 <= version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 <= version2); - EXPECT_FALSE(version2 <= version1); -} - -TEST(Version, Equal) { - Version version1, version2; - EXPECT_TRUE(version1 == version2); - EXPECT_TRUE(version2 == version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 == version2); - EXPECT_TRUE(version2 == version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 == version2); - EXPECT_FALSE(version2 == version1); -} - -TEST(Version, NotEqual) { - Version version1, version2; - EXPECT_FALSE(version1 != version2); - EXPECT_FALSE(version2 != version1); - - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 != version2); - EXPECT_FALSE(version2 != version1); - - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 != version2); - EXPECT_TRUE(version2 != version1); + EXPECT_EQ("1.0", text.value()); } } } diff --git a/src/tests/api/internals/main.cpp b/src/tests/api/internals/main.cpp index 5074ba32..4c99204c 100644 --- a/src/tests/api/internals/main.cpp +++ b/src/tests/api/internals/main.cpp @@ -33,7 +33,6 @@ #include "tests/api/internals/helpers/yaml_set_helpers_test.h" #include "tests/api/internals/masterlist_test.h" #include "tests/api/internals/metadata/condition_evaluator_test.h" -#include "tests/api/internals/metadata/condition_grammar_test.h" #include "tests/api/internals/metadata/conditional_metadata_test.h" #include "tests/api/internals/metadata/file_test.h" #include "tests/api/internals/metadata/group_test.h" diff --git a/src/tests/api/internals/metadata/condition_evaluator_test.h b/src/tests/api/internals/metadata/condition_evaluator_test.h index ca7e4676..91f152e5 100644 --- a/src/tests/api/internals/metadata/condition_evaluator_test.h +++ b/src/tests/api/internals/metadata/condition_evaluator_test.h @@ -40,9 +40,7 @@ protected: })), game_(GetParam(), dataPath.parent_path(), localPath), evaluator_(game_.Type(), - game_.DataPath(), - game_.GetCache(), - game_.GetLoadOrderHandler()), + game_.DataPath()), nonAsciiEsm(u8"non\u00C1scii.esm"), nonAsciiNestedFile(u8"non\u00C1scii/test.txt") { // Make sure the plugin with a non-ASCII filename exists. @@ -54,6 +52,10 @@ protected: std::ofstream out(nonAsciiPath); out.close(); + + loadInstalledPlugins(); + evaluator_.RefreshState(game_.GetCache()); + evaluator_.RefreshState(game_.GetLoadOrderHandler()); } std::string IntToHexString(const uint32_t value) { @@ -62,6 +64,31 @@ protected: return stream.str(); } + void loadInstalledPlugins() { + std::vector plugins({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + nonAsciiEsm, + }); + + if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) { + plugins.push_back(blankEsl); + } + + game_.IdentifyMainMasterFile(masterFile); + game_.LoadCurrentLoadOrderState(); + game_.LoadPlugins(plugins, true); + } + const std::vector info_; const std::string nonAsciiEsm; const std::string nonAsciiNestedFile; @@ -83,86 +110,61 @@ INSTANTIATE_TEST_CASE_P(, TEST_P(ConditionEvaluatorTest, evaluateShouldReturnTrueForAnEmptyConditionString) { - EXPECT_TRUE(evaluator_.evaluate("")); + EXPECT_TRUE(evaluator_.Evaluate("")); } TEST_P(ConditionEvaluatorTest, evaluateShouldThrowForAnInvalidConditionString) { - EXPECT_THROW(evaluator_.evaluate("condition"), ConditionSyntaxError); + EXPECT_THROW(evaluator_.Evaluate("condition"), ConditionSyntaxError); } TEST_P(ConditionEvaluatorTest, evaluateShouldReturnTrueForAConditionThatIsTrue) { - EXPECT_TRUE(evaluator_.evaluate("file(\"" + blankEsm + "\")")); + EXPECT_TRUE(evaluator_.Evaluate("file(\"" + blankEsm + "\")")); } TEST_P(ConditionEvaluatorTest, evaluateFileConditionShouldReturnTrueForANonAsciiFileThatExists) { - EXPECT_TRUE(evaluator_.evaluate("file(\"" + nonAsciiEsm + "\")")); + EXPECT_TRUE(evaluator_.Evaluate("file(\"" + nonAsciiEsm + "\")")); } TEST_P(ConditionEvaluatorTest, evaluateChecksumConditionShouldBeAbleToGetTheCrcOfANonAsciiFile) { std::string condition("checksum(\"" + nonAsciiEsm + "\", " + IntToHexString(blankEsmCrc) + ")"); - EXPECT_TRUE(evaluator_.evaluate(condition)); + EXPECT_TRUE(evaluator_.Evaluate(condition)); } TEST_P(ConditionEvaluatorTest, evaluateVersionConditionShouldBeAbleToGetTheVersionOfANonAsciiFile) { std::string condition("version(\"" + nonAsciiEsm + "\", \"5.0\", ==)"); - EXPECT_TRUE(evaluator_.evaluate(condition)); + EXPECT_TRUE(evaluator_.Evaluate(condition)); +} + +TEST_P(ConditionEvaluatorTest, + evaluateActiveConditionShouldReturnTrueForAnActivePlugin) { + std::string condition("active(\"" + blankEsm + "\")"); + EXPECT_TRUE(evaluator_.Evaluate(condition)); } TEST_P(ConditionEvaluatorTest, evaluateRegexFileConditionShouldReturnTrueForANonAsciiFileThatExists) { std::string condition(u8"file(\"non\u00C1scii.*\\.esm\")"); - EXPECT_TRUE(evaluator_.evaluate(condition)); + EXPECT_TRUE(evaluator_.Evaluate(condition)); } TEST_P(ConditionEvaluatorTest, evaluateRegexFileConditionShouldReturnTrueForANonAsciiNestedFileThatExists) { std::string condition(u8"file(\"non\u00C1scii/.+\\.txt\")"); - EXPECT_TRUE(evaluator_.evaluate(condition)); + EXPECT_TRUE(evaluator_.Evaluate(condition)); } TEST_P(ConditionEvaluatorTest, evaluateShouldReturnFalseForAConditionThatIsFalse) { - EXPECT_FALSE(evaluator_.evaluate("file(\"" + missingEsp + "\")")); -} - -TEST_P( - ConditionEvaluatorTest, - evaluateConditionShouldBeTrueIfTheCrcInThePluginCleaningDataGivenMatchesTheRealPluginCrc) { - PluginCleaningData dirtyInfo(blankEsmCrc, "cleaner", info_, 2, 10, 30); - - 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) { - PluginCleaningData dirtyInfo(0xDEADBEEF, "cleaner", info_, 2, 10, 30); - - EXPECT_FALSE(evaluator_.evaluate(dirtyInfo, blankEsm)); -} - -TEST_P(ConditionEvaluatorTest, - evaluateShouldBeFalseIfAnEmptyPluginFilenameIsGiven) { - PluginCleaningData dirtyInfo(blankEsmCrc, "cleaner", info_, 2, 10, 30); - - EXPECT_FALSE(evaluator_.evaluate(dirtyInfo, "")); + EXPECT_FALSE(evaluator_.Evaluate("file(\"" + missingEsp + "\")")); } TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { - PluginMetadata plugin(blankEsm); + PluginMetadata plugin(nonAsciiEsm); plugin.SetGroup("group1"); File file1(blankEsp); @@ -184,7 +186,7 @@ TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { plugin.SetDirtyInfo({info1, info2}); plugin.SetCleanInfo({info1, info2}); - EXPECT_NO_THROW(plugin = evaluator_.evaluateAll(plugin)); + EXPECT_NO_THROW(plugin = evaluator_.EvaluateAll(plugin)); std::set expectedFiles({file1}); EXPECT_EQ("group1", plugin.GetGroup().value()); @@ -200,7 +202,7 @@ TEST_P(ConditionEvaluatorTest, evaluateAllShouldEvaluateAllMetadataConditions) { TEST_P(ConditionEvaluatorTest, evaluateAllShouldPreserveGroupExplicitness) { PluginMetadata plugin(blankEsm); - EXPECT_NO_THROW(plugin = evaluator_.evaluateAll(plugin)); + EXPECT_NO_THROW(plugin = evaluator_.EvaluateAll(plugin)); EXPECT_FALSE(plugin.GetGroup()); } } diff --git a/src/tests/api/internals/metadata/condition_grammar_test.h b/src/tests/api/internals/metadata/condition_grammar_test.h deleted file mode 100644 index 7509a95c..00000000 --- a/src/tests/api/internals/metadata/condition_grammar_test.h +++ /dev/null @@ -1,727 +0,0 @@ -/* LOOT - -A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and -Fallout: New Vegas. - -Copyright (C) 2014-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 -. -*/ - -#ifndef LOOT_TESTS_API_INTERNALS_METADATA_CONDITION_GRAMMAR_TEST -#define LOOT_TESTS_API_INTERNALS_METADATA_CONDITION_GRAMMAR_TEST - -#include "api/metadata/condition_grammar.h" - -#include "tests/common_game_test_fixture.h" - -namespace loot { -namespace test { -class ConditionGrammarTest : public CommonGameTestFixture { -protected: - typedef ConditionGrammar - Grammar; - - ConditionGrammarTest() : - resourcePath(dataPath / "resource" / "detail" / "resource.txt"), - game_(GetParam(), dataPath.parent_path(), localPath), - evaluator_(game_.Type(), - game_.DataPath(), - game_.GetCache(), - game_.GetLoadOrderHandler()), - result_(false), - success_(false) {} - - inline void SetUp() { - CommonGameTestFixture::SetUp(); - - game_.LoadCurrentLoadOrderState(); - - // Write out an empty resource file. - ASSERT_NO_THROW( - std::filesystem::create_directories(resourcePath.parent_path())); - std::ofstream out(resourcePath); - out.close(); - ASSERT_TRUE(std::filesystem::exists(resourcePath)); - } - - std::string IntToHexString(const uint32_t value) { - std::stringstream stream; - stream << std::hex << value; - return stream.str(); - } - - void loadInstalledPlugins(Game& game_, bool headersOnly) { - const std::vector plugins({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }); - game_.LoadPlugins(plugins, headersOnly); - } - - const std::filesystem::path resourcePath; - - Game game_; - ConditionEvaluator evaluator_; - boost::spirit::qi::space_type skipper_; - bool result_; - bool success_; -}; - -// 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. -INSTANTIATE_TEST_CASE_P(, - ConditionGrammarTest, - ::testing::Values(GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4, - GameType::tes5se)); - -TEST_P(ConditionGrammarTest, parsingInvalidSyntaxShouldThrow) { - ConditionEvaluator evaluator; - Grammar grammar(evaluator); - std::string condition("file(foo)"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, evaluatingInvalidSyntaxShouldThrow) { - Grammar grammar(evaluator_); - std::string condition("file(foo)"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, parsingAnEmptyConditionShouldThrow) { - ConditionEvaluator evaluator; - Grammar grammar(evaluator); - std::string condition(""); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, evaluatingAnEmptyConditionShouldThrow) { - Grammar grammar(evaluator_); - std::string condition(""); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, - aFileConditionWithAPluginThatExistsShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - aFileConditionWithAPluginThatDoesNotExistShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + missingEsp + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - evaluatingAFileConditionForAnUnsafePathShouldThrow) { - Grammar grammar(evaluator_); - std::string condition("file(\"../../" + blankEsm + "\")"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, aFileConditionWithAnInvalidRegexShouldThrow) { - Grammar grammar(evaluator_); - std::string condition("file(\"RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp\")"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper_, - result_), - ConditionSyntaxError); -} - -TEST_P(ConditionGrammarTest, - aFileConditionWithARegexMatchingAPluginThatExistsShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("file(\"Blank.+\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aFileConditionWithARegexMatchingAPluginThatDoesNotExistShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("file(\"Blank\\.m.+\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aFileConditionWithARegexMatchingAFileInASubfolderThatExistsShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("file(\"resource/detail/resource\\.txt\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - aManyConditionWithARegexMatchingMoreThanOnePluginShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("many(\"Blank.+\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - aManyConditionWithARegexMatchingOnlyOnePluginShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("many(\"Blank\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aChecksumConditionWithACrcThatMatchesTheActualPluginCrcShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("checksum(\"" + blankEsm + "\", " + - IntToHexString(blankEsmCrc) + ")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aChecksumConditionWithACrcThatMatchesTheActualCachedPluginCrcShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, false)); - - Grammar grammar(evaluator_); - std::string condition("checksum(\"" + blankEsm + "\", " + - IntToHexString(blankEsmCrc) + ")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aChecksumConditionWithACrcThatDoesNotMatchTheActualPluginCrcShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("checksum(\"" + blankEsm + "\", DEADBEEF)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionEqualityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", ==)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionEqualityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", ==)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - aVersionEqualityConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"6.0\", ==)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionInequalityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", !=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionInequalityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", !=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - aVersionInequalityConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"6.0\", !=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionLessThanConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", <)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionLessThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", <)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - aVersionLessThanConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", <)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"4.0\", >)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", >)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", >)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionLessThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", <=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionLessThanOrEqualToConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"4.0\", <=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionLessThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", <=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", >=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", >=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aVersionGreaterThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(loadInstalledPlugins(game_, true)); - - Grammar grammar(evaluator_); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", >=)"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - anActiveConditionWithAPluginThatIsActiveShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("active(\"" + blankEsm + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - anActiveConditionWithAPluginThatIsNotActiveShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("active(\"" + blankEsp + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - anActiveConditionWithARegexMatchingAnActivePluginShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("active(\"Blank\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - anActiveConditionWithARegexMatchingNoActivePluginsShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("active(\"Blank\\.esp\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aManyActiveConditionWithARegexMatchingMoreThanOnePluginThatIsActiveShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition( - "many_active(\"Blank( - Different Master Dependent)?\\.es(m|p)\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P( - ConditionGrammarTest, - aManyActiveConditionWithARegexMatchingOnlyOnePluginThatIsActiveShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("many_active(\"Blank\\.esm\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P( - ConditionGrammarTest, - aManyActiveConditionWithARegexMatchingNoPluginsThatAreActiveShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("many_active(\"Blank\\.esp\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - aFalseConditionPrecededByANegatorShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("not file(\"" + missingEsp + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - aTrueConditionPrecededByANegatorShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("not file(\"" + blankEsm + "\")"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(condition), std::cend(condition), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - twoTrueConditionsJoinedByAnAndShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound(condition + " and " + condition); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - aTrueAndAFalseConditionJoinedByAnAndShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound(condition + " and not " + condition); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, - aFalseAndATrueConditionJoinedByAnOrShouldEvaluateToTrue) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " or " + condition); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, - twoFalseConditionsJoinedByAnOrShouldEvaluateToFalse) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " or not " + condition); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} - -TEST_P(ConditionGrammarTest, andOperatorsShouldTakePrecedenceOverOrOperators) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " and " + condition + " or " + - condition); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_TRUE(result_); -} - -TEST_P(ConditionGrammarTest, parenthesesShouldTakePrecedenceOverAndOperators) { - Grammar grammar(evaluator_); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " and ( " + condition + " or " + - condition + " )"); - - success_ = boost::spirit::qi::phrase_parse( - std::cbegin(compound), std::cend(compound), grammar, skipper_, result_); - EXPECT_TRUE(success_); - EXPECT_FALSE(result_); -} -} -} - -#endif diff --git a/src/tests/api/internals/metadata_list_test.h b/src/tests/api/internals/metadata_list_test.h index 9b712fbf..99820f05 100644 --- a/src/tests/api/internals/metadata_list_test.h +++ b/src/tests/api/internals/metadata_list_test.h @@ -361,12 +361,10 @@ TEST_P(MetadataListTest, TEST_P( MetadataListTest, - evalAllConditionsShouldEvaluateTheConditionsForThePluginsStoredInTeMetadataList) { + evalAllConditionsShouldEvaluateTheConditionsForThePluginsStoredInTheMetadataList) { Game game(GetParam(), dataPath.parent_path(), localPath); ConditionEvaluator evaluator(game.Type(), - game.DataPath(), - game.GetCache(), - game.GetLoadOrderHandler()); + game.DataPath()); MetadataList metadataList; ASSERT_NO_THROW(metadataList.Load(metadataPath));