diff --git a/CMakeLists.txt b/CMakeLists.txt index ee6bbe59..338e9a60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/fixtures.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/helpers/test_helpers.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/helpers/test_language.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/helpers/test_yaml_set_helpers.h" + "${CMAKE_SOURCE_DIR}/src/tests/backend/metadata/test_condition_grammar.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/metadata/test_file.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/metadata/test_formid.h" "${CMAKE_SOURCE_DIR}/src/tests/backend/metadata/test_location.h" diff --git a/src/tests/backend/metadata/test_condition_grammar.h b/src/tests/backend/metadata/test_condition_grammar.h new file mode 100644 index 00000000..311bca14 --- /dev/null +++ b/src/tests/backend/metadata/test_condition_grammar.h @@ -0,0 +1,608 @@ +/* LOOT + +A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and +Fallout: New Vegas. + +Copyright (C) 2014-2015 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_TEST_BACKEND_METADATA_CONDITION_GRAMMAR +#define LOOT_TEST_BACKEND_METADATA_CONDITION_GRAMMAR + +#include "backend/error.h" +#include "backend/metadata/condition_grammar.h" +#include "tests/fixtures.h" + +class ConditionGrammar : public SkyrimTest {}; + +typedef loot::ConditionGrammar Grammar; + +TEST_F(ConditionGrammar, Constructor) { + EXPECT_NO_THROW(Grammar cg(nullptr, true)); + EXPECT_THROW(Grammar cg(nullptr, false), loot::error); + + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + EXPECT_NO_THROW(Grammar cg(&game, true)); + EXPECT_NO_THROW(Grammar cg(&game, false)); +} + +TEST_F(ConditionGrammar, InvalidSyntax) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(foo)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval), loot::error); +} + +TEST_F(ConditionGrammar, EmptyCondition) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition(""); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval), loot::error); +} + +TEST_F(ConditionGrammar, FileConditionTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, FileConditionFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.missing.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, UnsafePath) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"../../Blank.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval), loot::error); +} + +TEST_F(ConditionGrammar, RegexConditionTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("regex(\"Blank.+\\.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, RegexConditionFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("regex(\"Blank\\.m.+\\.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, ChecksumConditionTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("checksum(\"Blank.esp\", 0B5B7B90)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, ChecksumConditionFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("checksum(\"Blank.esp\", DEADBEEF)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionEqualTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", ==)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionEqualFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"6.0\", ==)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionNotEqualTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"6.0\", !=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionNotEqualFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", !=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionLessThanTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"6.0\", <)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionLessThanFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", <)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionGreaterThanTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"4.0\", >)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionGreaterThanFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", >)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionLETrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", <=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionLEFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"4.0\", <=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionGETrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"5.0\", >=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, VersionConditionGEFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.LoadPlugins(true)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("version(\"Blank.esm\", \"6.0\", >=)"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, ActiveConditionTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("active(\"Blank.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, ActiveConditionFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("active(\"Blank.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, NegatorTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = false; + bool r = false; + Grammar cg(&game, false); + + std::string condition("not file(\"Blank.missing.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, NegatorFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("not file(\"Blank.esm\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, CompoundAndTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.esm\") and file(\"Blank.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, CompoundAndFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.esm\") and file(\"Blank.missing.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, CompoundOrTrue) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.missing.esm\") or file(\"Blank.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +TEST_F(ConditionGrammar, CompoundOrFalse) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.missing.esm\") or file(\"Blank.missing.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_FALSE(eval); +} + +TEST_F(ConditionGrammar, OrderOfEvaluation) { + loot::Game game(loot::Game::tes5); + game.SetGamePath(dataPath.parent_path()); + + boost::spirit::qi::space_type skipper; + bool eval = true; + bool r = false; + Grammar cg(&game, false); + + std::string condition("file(\"Blank.esm\") and ( not file(\"Blank.esm\") or file(\"Blank.esp\") ) or file(\"Blank.missing.esp\")"); + std::string::const_iterator begin = condition.begin(); + std::string::const_iterator end = condition.end(); + + EXPECT_NO_THROW(r = boost::spirit::qi::phrase_parse(begin, end, cg, skipper, eval)); + EXPECT_TRUE(r); + EXPECT_TRUE(eval); +} + +#endif diff --git a/src/tests/main.cpp b/src/tests/main.cpp index ee09037b..c2578898 100644 --- a/src/tests/main.cpp +++ b/src/tests/main.cpp @@ -31,6 +31,7 @@ #include "backend/game/test_game.h" #include "backend/game/test_game_settings.h" #include "backend/game/test_load_order_handler.h" +#include "backend/metadata/test_condition_grammar.h" #include "backend/metadata/test_file.h" #include "backend/metadata/test_formid.h" #include "backend/metadata/test_location.h"