Merge branch 'unit-tests' into v0.7-alpha

Conflicts:
	.travis.yml
This commit is contained in:
Oliver Hamlet
2014-11-01 13:20:11 +00:00
17 changed files with 651 additions and 65 deletions
+6 -2
View File
@@ -50,10 +50,14 @@ install:
before_script:
- mkdir build && cd build
# Fetch plugins to test with.
- wget https://github.com/WrinklyNinja/testing-plugins/archive/master.zip
- unzip master.zip
- mv testing-plugins-master/* ./
# Travis machines are 64 bit, and the deps use dynamic linking.
- cmake .. -DPROJECT_ARCH=64 -DPROJECT_STATIC_RUNTIME=OFF -DBUILD_SHARED_LIBS=OFF
- cmake .. -DPROJECT_ARCH=64 -DPROJECT_STATIC_RUNTIME=OFF -DBUILD_SHARED_LIBS=OFF -DGTEST_ROOT=../../gtest-1.7.0
script: make loot64
script: make tests && ./tests
notifications:
irc:
+56 -17
View File
@@ -46,7 +46,12 @@ set (Boost_USE_STATIC_LIBS ${PROJECT_STATIC_RUNTIME})
set (Boost_USE_MULTITHREADED ON)
set (Boost_USE_STATIC_RUNTIME ${PROJECT_STATIC_RUNTIME})
IF (NOT Boost_USE_STATIC_LIBS)
add_definitions(-DBOOST_LOG_DYN_LINK)
ENDIF ()
find_package(Boost REQUIRED COMPONENTS log log_setup regex locale thread date_time chrono filesystem system iostreams)
find_package(GTest)
set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/game.cpp"
@@ -88,7 +93,12 @@ set (LOOT_API_SRC ${LOOT_SRC}
set (LOOT_API_HEADERS ${LOOT_HEADERS}
"${CMAKE_SOURCE_DIR}/src/api/api.h")
source_group("Header Files" FILES ${LOOT_HEADERS} ${LOOT_GUI_HEADERS} ${LOOT_API_HEADERS})
set (LOOT_TESTS_SRC "${CMAKE_SOURCE_DIR}/src/tests/main.cpp")
set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/fixtures.h"
"${CMAKE_SOURCE_DIR}/src/tests/api/api.h")
source_group("Header Files" FILES ${LOOT_HEADERS} ${LOOT_GUI_HEADERS} ${LOOT_API_HEADERS} ${LOOT_TESTS_HEADERS})
# Include source and library directories.
include_directories ("${CMAKE_SOURCE_DIR}/src"
@@ -98,7 +108,8 @@ include_directories ("${CMAKE_SOURCE_DIR}/src"
"${LIBGIT2_ROOT}/include"
${CEF_ROOT}
${LIBESPM_ROOT}
${Boost_INCLUDE_DIRS})
${Boost_INCLUDE_DIRS}
${GTEST_INCLUDE_DIRS})
link_directories ("${LIBLOADORDER_ROOT}/build"
"${LIBGIT2_ROOT}/build"
@@ -113,12 +124,7 @@ link_directories ("${LIBLOADORDER_ROOT}/build"
# Settings when compiling for Windows. Since it's a Windows-only app this is always true, but useful to check for copy/paste into other projects.
IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
add_definitions (-DUNICODE -D_UNICODE -DNDEBUG -DLIBLO_STATIC -DWIN32 -D_WINDOWS)
IF (BUILD_SHARED_LIBS)
add_definitions (-DLOOT_EXPORT)
ELSE ()
add_definitions (-DLOOT_STATIC)
ENDIF ()
add_definitions (-DUNICODE -D_UNICODE -DLIBLO_STATIC)
ENDIF ()
# GCC and MinGW settings.
@@ -144,8 +150,8 @@ IF (MINGW)
set (LOOT_LIBS ${LOOT_LIBS}
version
ws2_32
shlwapi)
ws2_32
shlwapi)
set (LOOT_GUI_LIBS ${LOOT_LIBS}
cef_sandbox
libcef
@@ -165,13 +171,12 @@ ELSEIF (MSVC)
ENDFOREACH()
ENDIF ()
set (CMAKE_EXE_LINKER_FLAGS "/SUBSYSTEM:WINDOWS /LARGEADDRESSAWARE")
set (LOOT_LIBS git2
libyaml-cppmt
version
loadorder${PROJECT_ARCH}
ws2_32
shlwapi)
libyaml-cppmt
version
loadorder${PROJECT_ARCH}
ws2_32
shlwapi)
set (LOOT_GUI_LIBS ${LOOT_LIBS}
cef_sandbox
libcef
@@ -182,17 +187,51 @@ ENDIF ()
##############################
# Actual Building
# Define Targets
##############################
# Build API.
add_library (loot${PROJECT_ARCH} ${LOOT_API_SRC} ${LOOT_API_HEADERS})
target_link_libraries (loot${PROJECT_ARCH} ${Boost_LIBRARIES} ${LOOT_LIBS})
IF (${GTEST_FOUND})
# Build tests.
add_executable(tests ${LOOT_TESTS_SRC} ${LOOT_TESTS_HEADERS})
target_link_libraries(tests loot${PROJECT_ARCH} ${Boost_LIBRARIES} ${LOOT_LIBS} ${GTEST_BOTH_LIBRARIES})
ENDIF ()
# Build application.
add_executable (LOOT ${LOOT_GUI_SRC} ${LOOT_GUI_HEADERS})
target_link_libraries (LOOT ${Boost_LIBRARIES} ${LOOT_GUI_LIBS})
##############################
# Set Target-Specific Flags
##############################
IF (MSVC)
set (LOOT_LINK_FLAGS "/SUBSYSTEM:WINDOWS /LARGEADDRESSAWARE")
get_target_property (EXISTING_LINK_FLAGS LOOT LINK_FLAGS)
IF (EXISTING_LINK_FLAGS)
set (LOOT_LINK_FLAGS "${EXISTING_LINK_FLAGS} ${LOOT_LINK_FLAGS}")
ENDIF ()
set_target_properties (LOOT PROPERTIES LINK_FLAGS ${LOOT_LINK_FLAGS})
ENDIF ()
IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
IF (BUILD_SHARED_LIBS)
set_target_properties (loot${PROJECT_ARCH} PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} LOOT_EXPORT")
ELSE ()
set_target_properties (loot${PROJECT_ARCH} PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} LOOT_STATIC")
ENDIF ()
ENDIF ()
##############################
# Post-Build Steps
##############################
add_custom_command(
TARGET LOOT
POST_BUILD
+1
View File
@@ -17,6 +17,7 @@ LOOT uses [CMake](http://cmake.org) to generate build files, and requires the fo
* [Alphanum](http://www.davekoelle.com/files/alphanum.hpp)
* [Boost](http://www.boost.org) v1.56.0
* [Chromium Embedded Framework](https://code.google.com/p/chromiumembedded/) branch 2062
* [Google Test](https://code.google.com/p/googletest/) v1.7: Required to build the LOOT API's tests, but not the API itself or the LOOT application.
* [Libespm](http://github.com/WrinklyNinja/libespm)
* [Libgit2](http://libgit2.github.com/) v0.21.1
* [Libloadorder](http://github.com/WrinklyNinja/libloadorder)
+2 -2
View File
@@ -119,7 +119,7 @@ INLINE_INHERITED_MEMB = NO
# path before files name in the file list and in the header files. If set
# to NO the shortest path that makes the file name unique will be used.
FULL_PATH_NAMES = YES
FULL_PATH_NAMES = NO
# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
# can be used to strip a user-defined part of the path. Stripping is
@@ -1237,7 +1237,7 @@ MAKEINDEX_CMD_NAME = makeindex
# LaTeX documents. This may be useful for small projects and may help to
# save some trees in general.
COMPACT_LATEX = NO
COMPACT_LATEX = YES
# The PAPER_TYPE tag can be used to set the paper type that is used
# by the printer. Possible values are: a4, letter, legal and
+37 -8
View File
@@ -36,12 +36,10 @@
#include <clocale>
#include <list>
#include <vector>
#include <regex>
#include <unordered_set>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
#include <boost/filesystem.hpp>
const unsigned int loot_ok = loot::error::ok;
@@ -55,6 +53,7 @@ const unsigned int loot_error_invalid_args = loot::error::invalid_args;
const unsigned int loot_error_no_tag_map = loot::error::no_tag_map;
const unsigned int loot_error_path_not_found = loot::error::path_not_found;
const unsigned int loot_error_no_game_detected = loot::error::no_game_detected;
const unsigned int loot_error_git_error = loot::error::git_error;
const unsigned int loot_error_windows_error = loot::error::windows_error;
const unsigned int loot_error_sorting_error = loot::error::sorting_error;
const unsigned int loot_return_max = loot_error_sorting_error;
@@ -201,7 +200,7 @@ LOOT_API void loot_cleanup() {
// Returns whether this version of LOOT supports the API from the given
// LOOT version. Abstracts LOOT API stability policy away from clients.
LOOT_API bool loot_is_compatible(const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch) {
return versionMajor == loot::g_version_major && versionMinor == loot::g_version_minor;
return versionMajor == loot::g_version_major;
}
// Returns the version string for this version of LOOT.
@@ -250,18 +249,30 @@ LOOT_API unsigned int loot_create_db(loot_db * const db,
boost::filesystem::path game_local_path = "";
if (gameLocalPath != nullptr)
game_local_path = gameLocalPath;
#ifndef _WIN32
else
return c_error(loot_error_invalid_args, "A local data path must be supplied on non-Windows platforms.");
#endif
loot_db retVal = {0};
try {
retVal = new _loot_db_int(clientGame, game_path, game_local_path);
}
// Check for valid paths.
if (gamePath != nullptr && !boost::filesystem::is_directory(gamePath))
return c_error(loot_error_invalid_args, "Given game path \"" + std::string(gamePath) + "\" is not a valid directory.");
if (gameLocalPath != nullptr && !boost::filesystem::is_directory(gameLocalPath))
return c_error(loot_error_invalid_args, "Given local data path \"" + std::string(gameLocalPath) + "\" is not a valid directory.");
*db = new _loot_db_int(clientGame, game_path, game_local_path);
}
catch (loot::error& e) {
return c_error(e);
}
catch (std::bad_alloc& e) {
return c_error(loot_error_no_mem, e.what());
}
*db = retVal;
catch (std::exception& e) {
return c_error(loot_error_invalid_args, e.what());
}
return loot_ok;
}
@@ -292,6 +303,9 @@ LOOT_API unsigned int loot_load_lists(loot_db db, const char * const masterlistP
// We don't want to update the masterlist too.
temp.MetadataList::Load(masterlistPath);
}
else {
return c_error(loot_error_path_not_found, std::string("The given masterlist path does not exist: ") + masterlistPath);
}
}
catch (std::exception& e) {
return c_error(loot_error_parse_fail, e.what());
@@ -300,7 +314,10 @@ LOOT_API unsigned int loot_load_lists(loot_db db, const char * const masterlistP
try {
if (userlistPath != nullptr) {
if (boost::filesystem::exists(userlistPath)) {
userTemp.Load(masterlistPath);
userTemp.Load(userlistPath);
}
else {
return c_error(loot_error_path_not_found, std::string("The given userlist path does not exist: ") + userlistPath);
}
}
}
@@ -347,6 +364,18 @@ LOOT_API unsigned int loot_load_lists(loot_db db, const char * const masterlistP
LOOT_API unsigned int loot_eval_lists(loot_db db, const unsigned int language) {
if (db == nullptr)
return c_error(loot_error_invalid_args, "Null pointer passed.");
if (language != loot_lang_any
&& language != loot_lang_english
&& language != loot_lang_spanish
&& language != loot_lang_russian
&& language != loot_lang_french
&& language != loot_lang_chinese
&& language != loot_lang_polish
&& language != loot_lang_brazilian_portuguese
&& language != loot_lang_finnish
&& language != loot_lang_german
&& language != loot_lang_danish)
return c_error(loot_error_invalid_args, "Invalid language code given.");
// Clear caches before evaluating conditions.
db->conditionCache.clear();
+3
View File
@@ -182,6 +182,7 @@ extern "C"
LOOT_API extern const unsigned int loot_error_no_tag_map; /**< No Bash Tag map has been generated yet. */
LOOT_API extern const unsigned int loot_error_path_not_found; /**< A file or folder path could not be found. */
LOOT_API extern const unsigned int loot_error_no_game_detected; /**< The given game could not be found. */
LOOT_API extern const unsigned int loot_error_git_error; /**< An error occurred while performing a git operation (updating or getting the masterlist version). */
LOOT_API extern const unsigned int loot_error_windows_error; /**< An error occurred during a call to the Windows API. */
LOOT_API extern const unsigned int loot_error_sorting_error; /**< An error occurred while sorting plugins. */
@@ -468,6 +469,8 @@ extern "C"
* @brief Get the given masterlist's revision.
* @details Getting a masterlist's revision is only possible if it is
* found inside a local Git repository.
* @param db
* The database the function acts on.
* @param masterlistPath
* A string containing the relative or absolute path to the masterlist
* file that should be queried.
+5 -5
View File
@@ -472,8 +472,9 @@ namespace loot {
void Game::InitLibloHandle() {
const char * gameLocalDataPath = nullptr;
if (!_gameLocalDataPath.empty())
gameLocalDataPath = _gameLocalDataPath.string().c_str();
std::string localAppData = _gameLocalDataPath.string();
if (!localAppData.empty())
gameLocalDataPath = localAppData.c_str();
int ret;
if (Id() == Game::tes4)
@@ -510,6 +511,8 @@ namespace loot {
string err;
lo_get_error_message(&e);
lo_destroy_handle(gh);
gh = nullptr;
if (e == nullptr) {
BOOST_LOG_TRIVIAL(error) << "libloadorder failed to initialise game master file support. Details could not be fetched.";
err = lc::translate("libloadorder failed to initialise game master file support. Details could not be fetched.").str();
@@ -534,7 +537,6 @@ namespace loot {
const char * e = nullptr;
string err;
lo_get_error_message(&e);
lo_destroy_handle(gh);
if (e == nullptr) {
BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the active plugins list. Details could not be fetched.";
err = lc::translate("libloadorder failed to get the active plugins list. Details could not be fetched.").str();
@@ -568,7 +570,6 @@ namespace loot {
const char * e = nullptr;
string err;
lo_get_error_message(&e);
lo_destroy_handle(gh);
if (e == nullptr) {
BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the load order. Details could not be fetched.";
err = lc::translate("libloadorder failed to get the load order. Details could not be fetched.").str();
@@ -595,7 +596,6 @@ namespace loot {
const char * e = nullptr;
string err;
lo_get_error_message(&e);
lo_destroy_handle(gh);
if (e == nullptr) {
BOOST_LOG_TRIVIAL(error) << "libloadorder failed to set the load order. Details could not be fetched.";
err = lc::translate("libloadorder failed to set the load order. Details could not be fetched.").str();
-2
View File
@@ -31,8 +31,6 @@ along with LOOT. If not, see
#include <boost/algorithm/string.hpp>
#include <boost/locale.hpp>
#include <regex>
using namespace std;
namespace YAML {
+21 -9
View File
@@ -40,6 +40,16 @@ namespace fs = boost::filesystem;
namespace lc = boost::locale;
namespace loot {
// Removes the read-only flag from some files in git repositories created by libgit2.
void FixRepoPermissions(const fs::path& path) {
BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path;
for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) {
BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path();
fs::permissions(it->path(), fs::add_perms | fs::owner_write | fs::group_write | fs::others_write);
}
}
struct git_handler {
public:
git_handler() :
@@ -58,7 +68,18 @@ namespace loot {
buf({0}) {}
~git_handler() {
string path;
if (repo != nullptr)
path = git_repository_path(repo);
free();
if (!path.empty()) {
try {
FixRepoPermissions(path);
}
catch (exception&) {}
}
}
void free() {
@@ -131,15 +152,6 @@ namespace loot {
return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0;
}
// Removes the read-only flag from some files in git repositories created by libgit2.
void FixRepoPermissions(const fs::path& path) {
BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path;
for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) {
BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path();
fs::permissions(it->path(), fs::add_perms | fs::owner_write | fs::group_write | fs::others_write);
}
}
int diffFileCallback(const git_diff_delta *delta, float progress, void * payload) {
BOOST_LOG_TRIVIAL(trace) << "Checking diff for: " << delta->old_file.path;
if (strcmp(delta->old_file.path, "masterlist.yaml") == 0) {
+4 -2
View File
@@ -32,6 +32,7 @@
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/locale.hpp>
#include <boost/regex.hpp>
#include <alphanum.hpp>
@@ -41,7 +42,6 @@
#include <cstdio>
#include <ctime>
#include <sstream>
#include <regex>
#ifdef _WIN32
# ifndef UNICODE
@@ -59,6 +59,9 @@ namespace loot {
using namespace std;
using boost::algorithm::replace_all;
using boost::algorithm::replace_first;
using boost::regex;
using boost::regex_match;
using boost::regex_search;
namespace karma = boost::spirit::karma;
namespace fs = boost::filesystem;
namespace lc = boost::locale;
@@ -200,7 +203,6 @@ namespace loot {
HWND owner = 0;
TCHAR path[MAX_PATH];
BOOST_LOG_TRIVIAL(trace) << "Getting path to %LOCALAPPDATA%.";
HRESULT res = SHGetFolderPath(owner, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path);
if (res == S_OK)
+2 -2
View File
@@ -29,13 +29,13 @@
#include <cstdint>
#include <string>
#include <regex>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
namespace loot {
/// Array used to try each of the expressions defined using
/// an iteration for each of them.
extern const std::regex version_checks[7];
extern const boost::regex version_checks[7];
//////////////////////////////////////////////////////////////////////////
// Helper functions
+3 -7
View File
@@ -25,13 +25,11 @@ along with LOOT. If not, see
#ifndef __LOOT_JSON__
#define __LOOT_JSON__
#include <yaml-cpp/yaml.h>
#include <regex>
#include <boost/regex.hpp>
namespace loot {
// Handy class for turning YAML objects into JSON and vice-versa.
class JSON {
public:
@@ -41,7 +39,6 @@ namespace loot {
}
inline static std::string stringify(const YAML::Node& yaml) {
YAML::Emitter out;
out.SetOutputCharset(YAML::EscapeNonAscii);
out.SetStringFormat(YAML::DoubleQuoted);
@@ -65,14 +62,13 @@ namespace loot {
// Using the definition at <http://www.json.org/>.
// Version numbers should be kept as strings though.
std::regex numbers("\"(?!version)([^\"]+)\": \"(-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\"", std::regex::ECMAScript);
boost::regex numbers("\"(?!version)([^\"]+)\": \"(-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\"", boost::regex::ECMAScript);
json = std::regex_replace(json, numbers, "\"$1\": $2");
json = boost::regex_replace(json, numbers, "\"$1\": $2");
return json;
}
};
}
#endif
+5 -2
View File
@@ -27,16 +27,19 @@
#include "parsers.h"
#include "streams.h"
#include <regex>
#include <src/libespm.h>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/locale.hpp>
#include <boost/regex.hpp>
using namespace std;
using boost::regex;
using boost::regex_match;
using boost::regex_search;
using boost::smatch;
namespace loot {
namespace lc = boost::locale;
+7 -7
View File
@@ -39,7 +39,7 @@
#include "error.h"
#include <cstdint>
#include <regex>
#include <boost/regex.hpp>
#include <yaml-cpp/yaml.h>
@@ -569,11 +569,11 @@ namespace loot {
BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
std::regex sepReg("/|(\\\\\\\\)", std::regex::ECMAScript | std::regex::icase);
boost::regex sepReg("/|(\\\\\\\\)", boost::regex::ECMAScript | boost::regex::icase);
std::vector<std::string> components;
std::sregex_token_iterator it(regexStr.begin(), regexStr.end(), sepReg, -1);
std::sregex_token_iterator itend;
boost::sregex_token_iterator it(regexStr.begin(), regexStr.end(), sepReg, -1);
boost::sregex_token_iterator itend;
for (; it != itend; ++it) {
components.push_back(*it);
}
@@ -603,9 +603,9 @@ namespace loot {
return;
}
std::regex reg;
boost::regex reg;
try {
reg = std::regex(filename, std::regex::ECMAScript | std::regex::icase);
reg = boost::regex(filename, boost::regex::ECMAScript | boost::regex::icase);
}
catch (std::exception& /*e*/) {
BOOST_LOG_TRIVIAL(error) << "Invalid regex string:" << filename;
@@ -613,7 +613,7 @@ namespace loot {
}
for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
if (regex_match(itr->path().filename().string(), reg)) {
if (boost::regex_match(itr->path().filename().string(), reg)) {
result = true;
BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
return;
+228
View File
@@ -0,0 +1,228 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2014 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
<http://www.gnu.org/licenses/>.
*/
#ifndef LOOT_TEST_API
#define LOOT_TEST_API
#include "../../api/api.h"
#include "tests/fixtures.h"
TEST(GetVersion, HandlesNullInput) {
unsigned int vMajor, vMinor, vPatch;
EXPECT_EQ(loot_error_invalid_args, loot_get_version(&vMajor, NULL, NULL));
EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, &vMinor, NULL));
EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, &vPatch));
EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, NULL));
}
TEST(GetVersion, HandlesValidInput) {
unsigned int vMajor, vMinor, vPatch;
EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch));
}
TEST(IsCompatible, HandlesCompatibleVersion) {
unsigned int vMajor, vMinor, vPatch;
EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch));
EXPECT_TRUE(loot_is_compatible(vMajor, vMinor, vPatch));
// Test somewhat arbitrary variations.
EXPECT_TRUE(loot_is_compatible(vMajor, vMinor + 1, vPatch + 1));
if (vMinor > 0 && vPatch > 0)
EXPECT_TRUE(loot_is_compatible(vMajor, vMinor - 1, vPatch - 1));
}
TEST(IsCompatible, HandlesIncompatibleVersion) {
unsigned int vMajor, vMinor, vPatch;
EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch));
EXPECT_FALSE(loot_is_compatible(vMajor + 1, vMinor, vPatch));
// Test somewhat arbitrary variations.
EXPECT_FALSE(loot_is_compatible(vMajor + 1, vMinor + 1, vPatch + 1));
if (vMinor > 0 && vPatch > 0)
EXPECT_FALSE(loot_is_compatible(vMajor + 1, vMinor - 1, vPatch - 1));
}
TEST(GetErrorMessage, HandlesInputCorrectly) {
EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL));
const char * error;
EXPECT_EQ(loot_ok, loot_get_error_message(&error));
ASSERT_STREQ("Null message pointer passed.", error);
}
TEST(Cleanup, CleansUpAfterError) {
// First generate an error.
EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL));
// Check that the error message is non-null.
const char * error;
EXPECT_EQ(loot_ok, loot_get_error_message(&error));
ASSERT_STREQ("Null message pointer passed.", error);
ASSERT_NO_THROW(loot_cleanup());
// Now check that the error message pointer is null.
error = nullptr;
EXPECT_EQ(loot_ok, loot_get_error_message(&error));
EXPECT_EQ(nullptr, error);
}
TEST(Cleanup, HandlesNoError) {
ASSERT_NO_THROW(loot_cleanup());
const char * error = nullptr;
EXPECT_EQ(loot_ok, loot_get_error_message(&error));
EXPECT_EQ(nullptr, error);
}
TEST_F(OblivionTest, CreateDbHandlesValidInputs) {
EXPECT_EQ(loot_ok, loot_create_db(&db, loot_game_tes4, dataPath.parent_path().string().c_str(), localPath.string().c_str()));
ASSERT_NO_THROW(loot_destroy_db(db));
db = nullptr;
// Also test absolute paths.
boost::filesystem::path game = boost::filesystem::current_path() / dataPath.parent_path();
boost::filesystem::path local = boost::filesystem::current_path() / localPath;
EXPECT_EQ(loot_ok, loot_create_db(&db, loot_game_tes4, game.string().c_str(), local.string().c_str()));
}
TEST_F(OblivionTest, CreateDbHandlesInvalidHandleInput) {
EXPECT_EQ(loot_error_invalid_args, loot_create_db(NULL, loot_game_tes4, dataPath.parent_path().string().c_str(), localPath.string().c_str()));
}
TEST_F(OblivionTest, CreateDbHandlesInvalidGameType) {
EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, UINT_MAX, dataPath.parent_path().string().c_str(), localPath.string().c_str()));
}
TEST_F(OblivionTest, CreateDbHandlesInvalidGamePathInput) {
EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, loot_game_tes4, missingPath.string().c_str(), localPath.string().c_str()));
}
TEST_F(OblivionTest, CreateDbHandlesInvalidLocalPathInput) {
EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, loot_game_tes4, dataPath.parent_path().string().c_str(), missingPath.string().c_str()));
}
#ifdef _WIN32
TEST_F(OblivionTest, CreateDbHandlesNullLocalPath) {
EXPECT_EQ(loot_ok, loot_create_db(&db, loot_game_tes4, dataPath.parent_path().string().c_str(), NULL));
}
#endif
TEST(GameHandleDestroyTest, HandledNullInput) {
ASSERT_NO_THROW(loot_destroy_db(NULL));
}
TEST_F(OblivionAPIOperationsTest, UpdateMasterlist) {
bool updated;
EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(NULL, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", "master", &updated));
EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, NULL, "https://github.com/loot/oblivion.git", "master", &updated));
EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), NULL, "master", &updated));
EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", NULL, &updated));
EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", "master", NULL));
EXPECT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", "master", &updated));
EXPECT_TRUE(updated);
}
TEST_F(OblivionAPIOperationsTest, LoadLists) {
EXPECT_EQ(loot_error_invalid_args, loot_load_lists(NULL, masterlistPath.string().c_str(), NULL));
EXPECT_EQ(loot_error_invalid_args, loot_load_lists(db, NULL, NULL));
EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db, masterlistPath.string().c_str(), NULL));
bool updated;
ASSERT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", "master", &updated));
EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db, masterlistPath.string().c_str(), userlistPath.string().c_str()));
ASSERT_NO_THROW(boost::filesystem::copy(masterlistPath, userlistPath));
EXPECT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), userlistPath.string().c_str()));
}
TEST_F(OblivionAPIOperationsTest, EvalLists) {
// No lists loaded.
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_any));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_spanish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_russian));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_french));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_chinese));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_polish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_brazilian_portuguese));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_finnish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_german));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_danish));
// Invalid args.
EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(NULL, loot_lang_any));
EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(db, (unsigned int)-1));
// Now test different languages with a list loaded.
bool updated;
ASSERT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion.git", "master", &updated));
ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_any));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_spanish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_russian));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_french));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_chinese));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_polish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_brazilian_portuguese));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_finnish));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_german));
EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_danish));
}
TEST_F(OblivionAPIOperationsTest, SortPlugins) {
char ** sortedPlugins;
size_t numPlugins;
EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(NULL, &sortedPlugins, &numPlugins));
EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db, NULL, &numPlugins));
EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db, &sortedPlugins, NULL));
EXPECT_EQ(loot_ok, loot_sort_plugins(db, &sortedPlugins, &numPlugins));
// Expected order was obtained from running the API function once.
std::list<std::string> expectedOrder = {
"Blank.esm",
"Blank - Master Dependent.esm",
"Oblivion.esm",
"Blank - Different.esm",
"Blank - Different Master Dependent.esm",
"NotAPlugin.esm",
"EmptyFile.esm",
"Blank - Master Dependent.esp",
"Blank.esp",
"Blank - Plugin Dependent.esp",
"Blank - Different Master Dependent.esp",
"Blank - Different.esp",
"Blank - Different Plugin Dependent.esp"
};
std::list<std::string> actualOrder;
for (size_t i = 0; i < numPlugins; ++i) {
actualOrder.push_back(sortedPlugins[i]);
}
EXPECT_EQ(13, numPlugins);
EXPECT_EQ(expectedOrder, actualOrder);
}
#endif
+241
View File
@@ -0,0 +1,241 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2013-2014 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
<http://www.gnu.org/licenses/>.
*/
#ifndef LOOT_TEST_FIXTURES
#define LOOT_TEST_FIXTURES
#include "backend/streams.h"
#include <gtest/gtest.h>
#ifdef __GNUC__ // Workaround for GCC linking error.
#pragma message("GCC detected: Defining BOOST_NO_CXX11_SCOPED_ENUMS and BOOST_NO_SCOPED_ENUMS to avoid linking errors for boost::filesystem::copy_file().")
#define BOOST_NO_CXX11_SCOPED_ENUMS
#define BOOST_NO_SCOPED_ENUMS // For older versions.
#endif
#include <boost/filesystem.hpp>
class GameTest : public ::testing::Test {
protected:
GameTest(const boost::filesystem::path& gameDataPath, const boost::filesystem::path& gameLocalPath)
: dataPath(gameDataPath), localPath(gameLocalPath), missingPath("./missing"), masterlistPath(localPath / "masterlist.yaml"), userlistPath(localPath / "userlist.yaml"), db(nullptr) {}
inline virtual void SetUp() {
ASSERT_NO_THROW(boost::filesystem::create_directories(localPath));
ASSERT_TRUE(boost::filesystem::exists(localPath));
ASSERT_FALSE(boost::filesystem::exists(masterlistPath));
ASSERT_FALSE(boost::filesystem::exists(userlistPath));
ASSERT_FALSE(boost::filesystem::exists(localPath / ".git"));
ASSERT_FALSE(boost::filesystem::exists(missingPath));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Different.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Different Master Dependent.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank.esp"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Different.esp"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esp"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Different Master Dependent.esp"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Plugin Dependent.esp"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Different Plugin Dependent.esp"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Blank.esm.missing"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Blank.esp.missing"));
// Ghost a plugin.
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esm.ghost"));
ASSERT_NO_THROW(boost::filesystem::rename(dataPath / "Blank - Master Dependent.esm", dataPath / "Blank - Master Dependent.esm.ghost"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esm.ghost"));
// Write out an empty file.
loot::ofstream out(dataPath / "EmptyFile.esm");
out.close();
ASSERT_TRUE(boost::filesystem::exists(dataPath / "EmptyFile.esm"));
// Write out an non-empty, non-plugin file.
out.open(dataPath / "NotAPlugin.esm");
out << "This isn't a valid plugin file.";
out.close();
ASSERT_TRUE(boost::filesystem::exists(dataPath / "NotAPlugin.esm"));
}
inline virtual void TearDown() {
// Unghost the ghosted plugin.
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esm.ghost"));
ASSERT_NO_THROW(boost::filesystem::rename(dataPath / "Blank - Master Dependent.esm.ghost", dataPath / "Blank - Master Dependent.esm"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Blank - Master Dependent.esm.ghost"));
// Delete generated files.
ASSERT_NO_THROW(boost::filesystem::remove(dataPath / "EmptyFile.esm"));
ASSERT_NO_THROW(boost::filesystem::remove(dataPath / "NotAPlugin.esm"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "EmptyFile.esm"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "NotAPlugin.esm"));
// Masterlist & userlist may have been created during test, so delete them.
ASSERT_NO_THROW(boost::filesystem::remove(masterlistPath));
ASSERT_NO_THROW(boost::filesystem::remove(userlistPath));
// Also remove the ".git" folder if it has been created.
ASSERT_NO_THROW(boost::filesystem::remove_all(localPath / ".git"));
ASSERT_NO_THROW(loot_destroy_db(db));
}
const boost::filesystem::path dataPath;
const boost::filesystem::path localPath;
const boost::filesystem::path missingPath;
const boost::filesystem::path masterlistPath;
const boost::filesystem::path userlistPath;
loot_db db;
};
class OblivionTest : public GameTest {
protected:
OblivionTest() : GameTest("./Oblivion/Data", "./local/Oblivion") {}
inline virtual void SetUp() {
GameTest::SetUp();
// LOOT expects the game master file to be present, so mock it.
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Oblivion.esm"));
ASSERT_NO_THROW(boost::filesystem::copy_file(dataPath / "Blank.esm", dataPath / "Oblivion.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Oblivion.esm"));
// Oblivion's load order is decided through timestamps, so reset them to a known order before each test.
std::list<std::string> loadOrder = {
"Oblivion.esm",
"Blank.esm",
"Blank - Different.esm",
"Blank - Master Dependent.esm", // Ghosted
"Blank - Different Master Dependent.esm",
"Blank.esp",
"Blank - Different.esp",
"Blank - Master Dependent.esp",
"Blank - Different Master Dependent.esp",
"Blank - Plugin Dependent.esp",
"Blank - Different Plugin Dependent.esp"
};
time_t modificationTime = time(NULL); // Current time.
for (const auto &plugin : loadOrder) {
if (boost::filesystem::exists(dataPath / boost::filesystem::path(plugin + ".ghost"))) {
boost::filesystem::last_write_time(dataPath / boost::filesystem::path(plugin + ".ghost"), modificationTime);
}
else {
boost::filesystem::last_write_time(dataPath / plugin, modificationTime);
}
modificationTime += 60;
}
// Set Oblivion's active plugins to a known list before running the test.
loot::ofstream activePlugins(localPath / "plugins.txt");
activePlugins
<< "Oblivion.esm" << std::endl
<< "Blank.esm" << std::endl;
activePlugins.close();
}
inline virtual void TearDown() {
GameTest::TearDown();
// Delete the mock Oblivion.esm.
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Oblivion.esm"));
ASSERT_NO_THROW(boost::filesystem::remove(dataPath / "Oblivion.esm"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Oblivion.esm"));
// Delete existing plugins.txt.
ASSERT_NO_THROW(boost::filesystem::remove(localPath / "plugins.txt"));
};
};
class OblivionAPIOperationsTest : public OblivionTest {
protected:
inline virtual void SetUp() {
OblivionTest::SetUp();
ASSERT_EQ(loot_ok, loot_create_db(&db, loot_game_tes4, dataPath.parent_path().string().c_str(), localPath.string().c_str()));
}
};
class SkyrimTest : public GameTest {
protected:
SkyrimTest() : GameTest("./Skyrim/Data", "./local/Skyrim") {}
inline virtual void SetUp() {
GameTest::SetUp();
// Can't change Skyrim's main master file, so mock it.
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Skyrim.esm"));
ASSERT_NO_THROW(boost::filesystem::copy_file(dataPath / "Blank.esm", dataPath / "Skyrim.esm"));
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Skyrim.esm"));
// Set Skyrim's load order to a known list before running the test.
loot::ofstream loadOrder(localPath / "loadorder.txt");
loadOrder
<< "Skyrim.esm" << std::endl
<< "Blank.esm" << std::endl
<< "Blank - Different.esm" << std::endl
<< "Blank - Master Dependent.esm" << std::endl // Ghosted
<< "Blank - Different Master Dependent.esm" << std::endl
<< "Blank.esp" << std::endl
<< "Blank - Different.esp" << std::endl
<< "Blank - Master Dependent.esp" << std::endl
<< "Blank - Different Master Dependent.esp" << std::endl
<< "Blank - Plugin Dependent.esp" << std::endl
<< "Blank - Different Plugin Dependent.esp" << std::endl;
loadOrder.close();
// Set Skyrim's active plugins to a known list before running the test.
loot::ofstream activePlugins(localPath / "plugins.txt");
activePlugins
<< "Blank.esm" << std::endl;
activePlugins.close();
}
inline virtual void TearDown() {
GameTest::TearDown();
// Delete the mock Skyrim.esm.
ASSERT_TRUE(boost::filesystem::exists(dataPath / "Skyrim.esm"));
ASSERT_NO_THROW(boost::filesystem::remove(dataPath / "Skyrim.esm"));
ASSERT_FALSE(boost::filesystem::exists(dataPath / "Skyrim.esm"));
// Delete existing plugins.txt and loadorder.txt.
ASSERT_NO_THROW(boost::filesystem::remove(localPath / "plugins.txt"));
ASSERT_NO_THROW(boost::filesystem::remove(localPath / "loadorder.txt"));
};
};
class SkyrimAPIOperationsTest : public SkyrimTest {
protected:
inline virtual void SetUp() {
SkyrimTest::SetUp();
ASSERT_EQ(loot_ok, loot_create_db(&db, loot_game_tes5, dataPath.parent_path().string().c_str(), localPath.string().c_str()));
}
};
#endif
+30
View File
@@ -0,0 +1,30 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2014 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
<http://www.gnu.org/licenses/>.
*/
#include "tests/api/api.h"
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}