1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 06:52:34 +02:00

config: unknown data will be kept in memory and saved onto disk

This commit is contained in:
Anonymous Maarten 2017-02-20 20:57:36 +01:00 committed by Daniel Evans
parent 7a86c199b0
commit a92f24cbb4
3 changed files with 298 additions and 87 deletions

View File

@ -1,16 +1,16 @@
#include "GameConfig.hpp" #include "GameConfig.hpp"
#include <cstdlib> #include <algorithm>
#include <cstring>
#include <rw/defines.hpp> #include <rw/defines.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
const std::string kConfigDirectoryName("OpenRW"); const std::string kConfigDirectoryName("OpenRW");
GameConfig::GameConfig(const std::string& configName, GameConfig::GameConfig(const std::string &configName,
const std::string& configPath) const std::string &configPath)
: m_configName(configName) : m_configName(configName)
, m_configPath(configPath) , m_configPath(configPath)
, m_parseResult() , m_parseResult()
@ -23,7 +23,8 @@ GameConfig::GameConfig(const std::string& configName,
auto configFile = getConfigFile(); auto configFile = getConfigFile();
std::string dummy; std::string dummy;
m_parseResult = parseConfig(ParseType::FILE, configFile, ParseType::CONFIG, dummy); m_parseResult =
parseConfig(ParseType::FILE, configFile, ParseType::CONFIG, dummy);
} }
std::string GameConfig::getConfigFile() const { std::string GameConfig::getConfigFile() const {
@ -41,17 +42,17 @@ const GameConfig::ParseResult &GameConfig::getParseResult() const {
std::string GameConfig::getDefaultConfigPath() { std::string GameConfig::getDefaultConfigPath() {
#if defined(RW_LINUX) || defined(RW_FREEBSD) || defined(RW_NETBSD) || \ #if defined(RW_LINUX) || defined(RW_FREEBSD) || defined(RW_NETBSD) || \
defined(RW_OPENBSD) defined(RW_OPENBSD)
char* config_home = getenv("XDG_CONFIG_HOME"); char *config_home = getenv("XDG_CONFIG_HOME");
if (config_home != nullptr) { if (config_home != nullptr) {
return std::string(config_home) + "/" + kConfigDirectoryName; return std::string(config_home) + "/" + kConfigDirectoryName;
} }
char* home = getenv("HOME"); char *home = getenv("HOME");
if (home != nullptr) { if (home != nullptr) {
return std::string(home) + "/.config/" + kConfigDirectoryName; return std::string(home) + "/.config/" + kConfigDirectoryName;
} }
#elif defined(RW_OSX) #elif defined(RW_OSX)
char* home = getenv("HOME"); char *home = getenv("HOME");
if (home) if (home)
return std::string(home) + "/Library/Preferences/" + return std::string(home) + "/Library/Preferences/" +
kConfigDirectoryName; kConfigDirectoryName;
@ -67,7 +68,7 @@ std::string GameConfig::getDefaultConfigPath() {
std::string stripComments(const std::string &str) { std::string stripComments(const std::string &str) {
auto s = std::string(str, 0, str.find_first_of(";#")); auto s = std::string(str, 0, str.find_first_of(";#"));
return s.erase(s.find_last_not_of(" \n\r\t")+1); return s.erase(s.find_last_not_of(" \n\r\t") + 1);
} }
struct StringTranslator { struct StringTranslator {
@ -83,7 +84,7 @@ struct StringTranslator {
struct BoolTranslator { struct BoolTranslator {
typedef std::string internal_type; typedef std::string internal_type;
typedef bool external_type; typedef bool external_type;
boost::optional<external_type> get_value(const internal_type &str) { boost::optional<external_type> get_value(const internal_type &str) {
boost::optional<external_type> res; boost::optional<external_type> res;
try { try {
@ -99,7 +100,7 @@ struct BoolTranslator {
struct IntTranslator { struct IntTranslator {
typedef std::string internal_type; typedef std::string internal_type;
typedef int external_type; typedef int external_type;
boost::optional<external_type> get_value(const internal_type &str) { boost::optional<external_type> get_value(const internal_type &str) {
boost::optional<external_type> res; boost::optional<external_type> res;
try { try {
@ -115,8 +116,7 @@ struct IntTranslator {
GameConfig::ParseResult GameConfig::saveConfig() { GameConfig::ParseResult GameConfig::saveConfig() {
auto filename = getConfigFile(); auto filename = getConfigFile();
return parseConfig(ParseType::CONFIG, "", return parseConfig(ParseType::CONFIG, "", ParseType::FILE, filename);
ParseType::FILE, filename);
} }
std::string GameConfig::getDefaultINIString() { std::string GameConfig::getDefaultINIString() {
@ -125,10 +125,11 @@ std::string GameConfig::getDefaultINIString() {
return result; return result;
} }
GameConfig::ParseResult GameConfig::parseConfig( GameConfig::ParseResult GameConfig::parseConfig(GameConfig::ParseType srcType,
GameConfig::ParseType srcType, const std::string &source, const std::string &source,
ParseType destType, std::string &destination) ParseType destType,
{ std::string &destination) {
// srcTree: holds all key/value pairs
pt::ptree srcTree; pt::ptree srcTree;
ParseResult parseResult(srcType, source, destType, destination); ParseResult parseResult(srcType, source, destType, destination);
@ -152,12 +153,16 @@ GameConfig::ParseResult GameConfig::parseConfig(
return parseResult; return parseResult;
} }
// knownKeys: holds all known keys
std::vector<std::string> knownKeys;
auto read_config = [&](const std::string &key, auto &target, auto read_config = [&](const std::string &key, auto &target,
const auto &defaultValue, auto &translator, const auto &defaultValue, auto &translator,
bool optional=true) { bool optional = true) {
typedef typename std::remove_reference<decltype(target)>::type config_t; typedef typename std::remove_reference<decltype(target)>::type config_t;
config_t sourceValue; config_t sourceValue;
knownKeys.push_back(key);
switch (srcType) { switch (srcType) {
case ParseType::DEFAULT: case ParseType::DEFAULT:
@ -210,13 +215,50 @@ GameConfig::ParseResult GameConfig::parseConfig(
// Additionally, add them to the unit test. // Additionally, add them to the unit test.
// @todo Don't allow path separators and relative directories // @todo Don't allow path separators and relative directories
read_config("game.path", this->m_gamePath, "/opt/games/Grand Theft Auto 3", deft, false); read_config("game.path", this->m_gamePath, "/opt/games/Grand Theft Auto 3",
deft, false);
read_config("game.language", this->m_gameLanguage, "american", deft); read_config("game.language", this->m_gameLanguage, "american", deft);
read_config("input.invert_y", this->m_inputInvertY, false, boolt); read_config("input.invert_y", this->m_inputInvertY, false, boolt);
if (!parseResult.isValid()) if (!parseResult.isValid()) return parseResult;
return parseResult;
// Build the unknown key/value map from the correct source
switch (srcType) {
case ParseType::FILE:
case ParseType::STRING:
for (const auto &section : srcTree) {
for (const auto &subKey : section.second) {
std::string key = section.first + "." + subKey.first;
if (std::find(knownKeys.begin(), knownKeys.end(), key) ==
knownKeys.end()) {
RW_MESSAGE("Unknown configuration key: " << key);
parseResult.addUnknownData(key, subKey.second.data());
}
}
}
break;
case ParseType::CONFIG:
parseResult.setUnknownData(m_parseResult.getUnknownData());
break;
case ParseType::DEFAULT:
break;
}
// Store the unknown key/value map to the correct destination
switch (destType) {
case ParseType::CONFIG:
m_parseResult.setUnknownData(parseResult.getUnknownData());
break;
case ParseType::STRING:
case ParseType::FILE:
for (const auto &keyvalue : parseResult.getUnknownData()) {
srcTree.put(keyvalue.first, keyvalue.second);
}
break;
default:
break;
}
try { try {
if (destType == ParseType::STRING) { if (destType == ParseType::STRING) {
@ -234,8 +276,8 @@ GameConfig::ParseResult GameConfig::parseConfig(
return parseResult; return parseResult;
} }
std::string GameConfig::extractFilenameParseTypeData(ParseType type, const std::string &data) std::string GameConfig::extractFilenameParseTypeData(ParseType type,
{ const std::string &data) {
switch (type) { switch (type) {
case ParseType::CONFIG: case ParseType::CONFIG:
return "<configuration>"; return "<configuration>";
@ -249,16 +291,19 @@ std::string GameConfig::extractFilenameParseTypeData(ParseType type, const std::
} }
} }
GameConfig::ParseResult::ParseResult( GameConfig::ParseResult::ParseResult(GameConfig::ParseType srcType,
GameConfig::ParseType srcType, const std::string &source, const std::string &source,
GameConfig::ParseType destType, const std::string &destination) GameConfig::ParseType destType,
const std::string &destination)
: m_result(ErrorType::GOOD) : m_result(ErrorType::GOOD)
, m_inputfilename(GameConfig::extractFilenameParseTypeData(srcType, source)) , m_inputfilename(GameConfig::extractFilenameParseTypeData(srcType, source))
, m_outputfilename(GameConfig::extractFilenameParseTypeData(destType, destination)) , m_outputfilename(
GameConfig::extractFilenameParseTypeData(destType, destination))
, m_line(0) , m_line(0)
, m_message() , m_message()
, m_keys_requiredMissing() , m_keys_requiredMissing()
, m_keys_invalidData() { , m_keys_invalidData()
, m_unknownData() {
} }
GameConfig::ParseResult::ParseResult() GameConfig::ParseResult::ParseResult()
@ -280,7 +325,7 @@ bool GameConfig::ParseResult::isValid() const {
} }
void GameConfig::ParseResult::failInputFile(size_t line, void GameConfig::ParseResult::failInputFile(size_t line,
const std::string &message) { const std::string &message) {
this->m_result = ParseResult::ErrorType::INVALIDINPUTFILE; this->m_result = ParseResult::ErrorType::INVALIDINPUTFILE;
this->m_line = line; this->m_line = line;
this->m_message = message; this->m_message = message;
@ -301,17 +346,19 @@ void GameConfig::ParseResult::failInvalidData(const std::string &key) {
} }
void GameConfig::ParseResult::failOutputFile(size_t line, void GameConfig::ParseResult::failOutputFile(size_t line,
const std::string &message) { const std::string &message) {
this->m_result = ParseResult::ErrorType::INVALIDOUTPUTFILE; this->m_result = ParseResult::ErrorType::INVALIDOUTPUTFILE;
this->m_line = line; this->m_line = line;
this->m_message = message; this->m_message = message;
} }
const std::vector<std::string> &GameConfig::ParseResult::getKeysRequiredMissing() const { const std::vector<std::string>
&GameConfig::ParseResult::getKeysRequiredMissing() const {
return this->m_keys_requiredMissing; return this->m_keys_requiredMissing;
} }
const std::vector<std::string> &GameConfig::ParseResult::getKeysInvalidData() const { const std::vector<std::string> &GameConfig::ParseResult::getKeysInvalidData()
const {
return this->m_keys_invalidData; return this->m_keys_invalidData;
} }
@ -321,27 +368,23 @@ std::string GameConfig::ParseResult::what() const {
return "Good"; return "Good";
case ErrorType::INVALIDARGUMENT: case ErrorType::INVALIDARGUMENT:
return "Invalid argument: destination cannot be the default config"; return "Invalid argument: destination cannot be the default config";
case ErrorType::INVALIDINPUTFILE: case ErrorType::INVALIDINPUTFILE: {
{
std::ostringstream oss; std::ostringstream oss;
oss << "Error while reading \"" oss << "Error while reading \"" << this->m_inputfilename
<< this->m_inputfilename << "\":" << this->m_line << ":\n" << "\":" << this->m_line << ":\n"
<< this->m_message; << this->m_message;
return oss.str(); return oss.str();
} }
case ErrorType::INVALIDOUTPUTFILE: case ErrorType::INVALIDOUTPUTFILE: {
{
std::ostringstream oss; std::ostringstream oss;
oss << "Error while writing \"" oss << "Error while writing \"" << this->m_inputfilename
<< this->m_inputfilename << "\":" << this->m_line << ":\n" << "\":" << this->m_line << ":\n"
<< this->m_message; << this->m_message;
return oss.str(); return oss.str();
} }
case ErrorType::INVALIDCONTENT: case ErrorType::INVALIDCONTENT: {
{
std::ostringstream oss; std::ostringstream oss;
oss << "Error while parsing \"" oss << "Error while parsing \"" << this->m_inputfilename << "\".";
<< this->m_inputfilename << "\".";
if (this->m_keys_requiredMissing.size()) { if (this->m_keys_requiredMissing.size()) {
oss << "\nRequired keys that are missing:"; oss << "\nRequired keys that are missing:";
for (auto &key : this->m_keys_requiredMissing) { for (auto &key : this->m_keys_requiredMissing) {
@ -360,3 +403,18 @@ std::string GameConfig::ParseResult::what() const {
return "Unknown error"; return "Unknown error";
} }
} }
void GameConfig::ParseResult::addUnknownData(const std::string &key,
const std::string &value) {
this->m_unknownData[key] = value;
}
const std::map<std::string, std::string>
&GameConfig::ParseResult::getUnknownData() const {
return this->m_unknownData;
}
void GameConfig::ParseResult::setUnknownData(
const std::map<std::string, std::string> &unknownData) {
this->m_unknownData = unknownData;
}

View File

@ -1,47 +1,50 @@
#ifndef RWGAME_GAMECONFIG_HPP #ifndef RWGAME_GAMECONFIG_HPP
#define RWGAME_GAMECONFIG_HPP #define RWGAME_GAMECONFIG_HPP
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
class GameConfig { class GameConfig {
private: private:
enum ParseType { enum ParseType { DEFAULT, CONFIG, FILE, STRING };
DEFAULT,
CONFIG,
FILE,
STRING
};
/** /**
* @brief extractFilenameParseTypeData Get a human readable filename string * @brief extractFilenameParseTypeData Get a human readable filename string
* @return file path or a description of the data type * @return file path or a description of the data type
*/ */
static std::string extractFilenameParseTypeData(ParseType type, const std::string &data); static std::string extractFilenameParseTypeData(ParseType type,
const std::string &data);
public: public:
class ParseResult { class ParseResult {
public: public:
enum ErrorType { enum ErrorType {
/// GOOD: Input file/string was good /// GOOD: Input file/string was good
GOOD, GOOD,
/// INVALIDINPUTFILE: There was some error while reading from a file or string or the input was ambiguous (e.g. duplicate keys) /// INVALIDINPUTFILE: There was some error while reading from a file
/// or string or the input was ambiguous (e.g. duplicate keys)
INVALIDINPUTFILE, INVALIDINPUTFILE,
/// INVALIDARGUMENT: The parser received impossible arguments /// INVALIDARGUMENT: The parser received impossible arguments
INVALIDARGUMENT, INVALIDARGUMENT,
/// INVALIDCONTENT: Some required keys were missing or some values were of incorrect type /// INVALIDCONTENT: Some required keys were missing or some values
/// were of incorrect type
INVALIDCONTENT, INVALIDCONTENT,
/// INVALIDOUTPUTFILE: There was some error while writing to a file or string /// INVALIDOUTPUTFILE: There was some error while writing to a file
/// or string
INVALIDOUTPUTFILE INVALIDOUTPUTFILE
}; };
private: private:
/** /**
* @brief ParseResult Holds the issues occurred while parsing of a config file. * @brief ParseResult Holds the issues occurred while parsing of a
* config file.
* @param srcType Type of the source * @param srcType Type of the source
* @param source The source of the parser * @param source The source of the parser
* @param destType Type of the destination * @param destType Type of the destination
* @param destination The destination * @param destination The destination
*/ */
ParseResult(ParseType srcType, const std::string &source, ParseResult(ParseType srcType, const std::string &source,
ParseType destType, const std::string &destination); ParseType destType, const std::string &destination);
/** /**
* @brief ParseResult Create empty ParseResult * @brief ParseResult Create empty ParseResult
@ -93,7 +96,8 @@ public:
void failInvalidData(const std::string &key); void failInvalidData(const std::string &key);
/** /**
* @brief failOutputFile Fail because an error occurred while while writing to the output * @brief failOutputFile Fail because an error occurred while while
* writing to the output
* @param line Line number where the error is located * @param line Line number where the error is located
* @param message Description of the error * @param message Description of the error
*/ */
@ -110,6 +114,20 @@ public:
* @return String with the error description * @return String with the error description
*/ */
std::string what() const; std::string what() const;
/**
* @brief addUnknownData Add unknown key value pairs
* @param key The unknown key
* @param value The associated data
*/
void addUnknownData(const std::string &key, const std::string &value);
/**
* @brief addUnknownData Get all the unknown key value pairs
* @return Mapping of the unknown keys with associated data
*/
const std::map<std::string, std::string> &getUnknownData() const;
private: private:
/// Type of the failure /// Type of the failure
ErrorType m_result; ErrorType m_result;
@ -120,7 +138,8 @@ public:
/// Filename of the output file /// Filename of the output file
std::string m_outputfilename; std::string m_outputfilename;
/// Line number where the failure occurred (on invalid input or output file) /// Line number where the failure occurred (on invalid input or output
/// file)
size_t m_line; size_t m_line;
/// Description of the failure (on invalid input or output file) /// Description of the failure (on invalid input or output file)
@ -132,6 +151,15 @@ public:
/// All keys that contain invalid data /// All keys that contain invalid data
std::vector<std::string> m_keys_invalidData; std::vector<std::string> m_keys_invalidData;
// Mapping of unknown keys and associated data
std::map<std::string, std::string> m_unknownData;
/**
* @brief setUnknownData Replace the the unknown key value pairs
*/
void setUnknownData(
const std::map<std::string, std::string> &unknownData);
friend class GameConfig; friend class GameConfig;
}; };
@ -140,8 +168,8 @@ public:
* @param configName The configuration filename to load * @param configName The configuration filename to load
* @param configPath Where to look. * @param configPath Where to look.
*/ */
GameConfig(const std::string& configName, GameConfig(const std::string &configName,
const std::string& configPath = getDefaultConfigPath()); const std::string &configPath = getDefaultConfigPath());
/** /**
* @brief getFilePath Returns the system file path for the configuration * @brief getFilePath Returns the system file path for the configuration
@ -166,15 +194,16 @@ public:
const ParseResult &getParseResult() const; const ParseResult &getParseResult() const;
/** /**
* @brief getConfigString Returns the content of the default INI configuration. * @brief getConfigString Returns the content of the default INI
* configuration.
* @return INI string * @return INI string
*/ */
std::string getDefaultINIString(); std::string getDefaultINIString();
const std::string& getGameDataPath() const { const std::string &getGameDataPath() const {
return m_gamePath; return m_gamePath;
} }
const std::string& getGameLanguage() const { const std::string &getGameLanguage() const {
return m_gameLanguage; return m_gameLanguage;
} }
bool getInputInvertY() const { bool getInputInvertY() const {
@ -186,6 +215,7 @@ private:
/** /**
* @brief parseConfig Load data from source and write it to destination. * @brief parseConfig Load data from source and write it to destination.
* Whitespace will be stripped from unknown data.
* @param srcType Can be DEFAULT | CONFIG | FILE | STRING * @param srcType Can be DEFAULT | CONFIG | FILE | STRING
* @param source don't care if srcType == (DEFAULT | CONFIG), * @param source don't care if srcType == (DEFAULT | CONFIG),
* path of INI file if srcType == FILE * path of INI file if srcType == FILE
@ -197,7 +227,7 @@ private:
* @return True if the parsing succeeded * @return True if the parsing succeeded
*/ */
ParseResult parseConfig(ParseType srcType, const std::string &source, ParseResult parseConfig(ParseType srcType, const std::string &source,
ParseType destType, std::string &destination); ParseType destType, std::string &destination);
/* Config State */ /* Config State */
std::string m_configName; std::string m_configName;

View File

@ -3,12 +3,39 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include "rw/defines.hpp"
namespace pt = boost::property_tree;
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
typedef std::map<std::string, std::map<std::string, std::string>> simpleConfig_t; typedef std::map<std::string, std::map<std::string, std::string>>
simpleConfig_t;
simpleConfig_t readConfig(const std::string &filename) {
simpleConfig_t cfg;
pt::ptree tree;
pt::read_ini(filename, tree);
for (const auto &section : tree) {
for (const auto &subKey : section.second) {
cfg[section.first][subKey.first] = subKey.second.data();
}
}
return cfg;
}
std::string stripWhitespace(const std::string &in) {
static const std::string whitespace = " \n\r\t";
auto start = in.find_first_not_of(whitespace);
auto end = in.find_last_not_of(whitespace) + 1;
return std::string(in, start, end - start);
}
simpleConfig_t getValidConfig() { simpleConfig_t getValidConfig() {
simpleConfig_t result; simpleConfig_t result;
@ -16,14 +43,17 @@ simpleConfig_t getValidConfig() {
// to test the robustness of the INI parser. // to test the robustness of the INI parser.
// Don't change game.path and input.invert_y keys. Tests depend on them. // Don't change game.path and input.invert_y keys. Tests depend on them.
result["game"]["path"] = "\t/dev/test \t \r\n"; result["game"]["path"] = "\t/dev/test \t \r\n";
result["game"]["\tlanguage\t "] = " american ;american english french german italian spanish."; result["game"]["\tlanguage\t "] =
result["input"]["invert_y"] = "1 #values != 0 enable input inversion. Optional."; " american ;american english french german italian spanish.";
result["input"]["invert_y"] =
"1 #values != 0 enable input inversion. Optional.";
return result; return result;
} }
std::ostream &operator<<(std::ostream &os, const simpleConfig_t &config) { std::ostream &operator<<(std::ostream &os, const simpleConfig_t &config) {
for (auto &section : config) { for (auto &section : config) {
os << "[" << section.first << "]" << "\n"; os << "[" << section.first << "]"
<< "\n";
for (auto &keyValue : section.second) { for (auto &keyValue : section.second) {
os << keyValue.first << "=" << keyValue.second << "\n"; os << keyValue.first << "=" << keyValue.second << "\n";
} }
@ -59,10 +89,11 @@ public:
return this->m_path.parent_path().string(); return this->m_path.parent_path().string();
} }
void change_perms_readonly() { void change_perms_readonly() {
fs::permissions(this->m_path, fs::perms::owner_read fs::permissions(this->m_path, fs::perms::owner_read |
| fs::perms::group_read | fs::perms::others_read); fs::perms::group_read |
fs::perms::others_read);
} }
template<typename T> template <typename T>
bool append(T t) { bool append(T t) {
// Append argument at the end of the file. // Append argument at the end of the file.
// File is open/closes repeatedly. Not optimal. // File is open/closes repeatedly. Not optimal.
@ -71,7 +102,7 @@ public:
ofs.close(); ofs.close();
return ofs.good(); return ofs.good();
} }
template<typename T> template <typename T>
bool write(T t) { bool write(T t) {
// Write the argument to the file, discarding all contents. // Write the argument to the file, discarding all contents.
// File is open/closes repeatedly. Not optimal. // File is open/closes repeatedly. Not optimal.
@ -80,15 +111,31 @@ public:
ofs.close(); ofs.close();
return ofs.good(); return ofs.good();
} }
private: private:
static fs::path getRandomFilePath() { static fs::path getRandomFilePath() {
return fs::unique_path(fs::temp_directory_path() / "openrw_test_%%%%%%%%%%%%%%%%"); return fs::unique_path(fs::temp_directory_path() /
"openrw_test_%%%%%%%%%%%%%%%%");
} }
fs::path m_path; fs::path m_path;
}; };
BOOST_AUTO_TEST_SUITE(ConfigTests) BOOST_AUTO_TEST_SUITE(ConfigTests)
BOOST_AUTO_TEST_CASE(test_stripWhitespace) {
std::map<std::string, std::string> map;
map["abc"] = "abc";
map["\tabc"] = "abc";
map["abc\t"] = "abc";
map[" abc"] = "abc";
map["abc "] = "abc";
map[" abc "] = "abc";
map[" abc "] = "abc";
for (const auto &keyValue : map) {
BOOST_CHECK_EQUAL(keyValue.second, stripWhitespace(keyValue.first));
}
}
BOOST_AUTO_TEST_CASE(test_TempFile) { BOOST_AUTO_TEST_CASE(test_TempFile) {
// Check the behavior of TempFile // Check the behavior of TempFile
TempFile tempFile; TempFile tempFile;
@ -126,8 +173,10 @@ BOOST_AUTO_TEST_CASE(test_config_valid) {
GameConfig config(tempFile.filename(), tempFile.dirname()); GameConfig config(tempFile.filename(), tempFile.dirname());
BOOST_CHECK(config.isValid()); BOOST_CHECK(config.isValid());
BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD); BOOST_CHECK_EQUAL(config.getParseResult().type(),
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0); GameConfig::ParseResult::ErrorType::GOOD);
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(),
0);
BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0); BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0);
BOOST_CHECK_EQUAL(config.getGameDataPath(), "/dev/test"); BOOST_CHECK_EQUAL(config.getGameDataPath(), "/dev/test");
@ -147,8 +196,10 @@ BOOST_AUTO_TEST_CASE(test_config_valid_modified) {
GameConfig config(tempFile.filename(), tempFile.dirname()); GameConfig config(tempFile.filename(), tempFile.dirname());
BOOST_CHECK(config.isValid()); BOOST_CHECK(config.isValid());
BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD); BOOST_CHECK_EQUAL(config.getParseResult().type(),
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0); GameConfig::ParseResult::ErrorType::GOOD);
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(),
0);
BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0); BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0);
BOOST_CHECK_EQUAL(config.getInputInvertY(), false); BOOST_CHECK_EQUAL(config.getInputInvertY(), false);
@ -176,6 +227,55 @@ BOOST_AUTO_TEST_CASE(test_config_save) {
GameConfig config2(tempFile.filename(), tempFile.dirname()); GameConfig config2(tempFile.filename(), tempFile.dirname());
BOOST_CHECK_EQUAL(config2.getGameDataPath(), "Liberty City"); BOOST_CHECK_EQUAL(config2.getGameDataPath(), "Liberty City");
simpleConfig_t cfg2 = readConfig(tempFile.path());
BOOST_CHECK_EQUAL(cfg2["game"]["path"], "Liberty City");
}
BOOST_AUTO_TEST_CASE(test_config_valid_unknown_keys) {
// Test reading a valid modified configuration file with unknown data
auto cfg = getValidConfig();
cfg["game"]["unknownkey"] = "descartes";
cfg["dontknow"]["dontcare"] = "\t$%!$8847 %%$ ";
TempFile tempFile;
tempFile.append(cfg);
GameConfig config(tempFile.filename(), tempFile.dirname());
BOOST_CHECK(config.isValid());
const auto &unknownData = config.getParseResult().getUnknownData();
BOOST_CHECK_EQUAL(unknownData.size(), 2);
BOOST_CHECK_EQUAL(unknownData.count("game.unknownkey"), 1);
BOOST_CHECK_EQUAL(unknownData.at("game.unknownkey"),
stripWhitespace(cfg["game"]["unknownkey"]));
BOOST_CHECK_EQUAL(unknownData.count("dontknow.dontcare"), 1);
BOOST_CHECK_EQUAL(unknownData.at("dontknow.dontcare"),
stripWhitespace(cfg["dontknow"]["dontcare"]));
BOOST_CHECK_EQUAL(unknownData.count("game.path"), 0);
tempFile.remove();
config.saveConfig();
GameConfig config2(tempFile.filename(), tempFile.dirname());
const auto &unknownData2 = config2.getParseResult().getUnknownData();
BOOST_CHECK_EQUAL(unknownData2.size(), 2);
BOOST_CHECK_EQUAL(unknownData2.count("game.unknownkey"), 1);
BOOST_CHECK_EQUAL(unknownData2.at("game.unknownkey"),
stripWhitespace(cfg["game"]["unknownkey"]));
BOOST_CHECK_EQUAL(unknownData2.count("dontknow.dontcare"), 1);
BOOST_CHECK_EQUAL(unknownData2.at("dontknow.dontcare"),
stripWhitespace(cfg["dontknow"]["dontcare"]));
BOOST_CHECK_EQUAL(unknownData2.count("game.path"), 0);
} }
BOOST_AUTO_TEST_CASE(test_config_save_readonly) { BOOST_AUTO_TEST_CASE(test_config_save_readonly) {
@ -191,7 +291,8 @@ BOOST_AUTO_TEST_CASE(test_config_save_readonly) {
auto writeResult = config.saveConfig(); auto writeResult = config.saveConfig();
BOOST_CHECK(!writeResult.isValid()); BOOST_CHECK(!writeResult.isValid());
BOOST_CHECK_EQUAL(writeResult.type(), GameConfig::ParseResult::ErrorType::INVALIDOUTPUTFILE); BOOST_CHECK_EQUAL(writeResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDOUTPUTFILE);
} }
BOOST_AUTO_TEST_CASE(test_config_valid_default) { BOOST_AUTO_TEST_CASE(test_config_valid_default) {
@ -209,6 +310,22 @@ BOOST_AUTO_TEST_CASE(test_config_valid_default) {
BOOST_CHECK(config.isValid()); BOOST_CHECK(config.isValid());
} }
BOOST_AUTO_TEST_CASE(test_config_invalid_emptykey) {
// Test duplicate keys in invalid configuration file
auto cfg = getValidConfig();
cfg["game"][""] = "0";
TempFile tempFile;
tempFile.append(cfg);
GameConfig config(tempFile.filename(), tempFile.dirname());
BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE);
}
BOOST_AUTO_TEST_CASE(test_config_invalid_duplicate) { BOOST_AUTO_TEST_CASE(test_config_invalid_duplicate) {
// Test duplicate keys in invalid configuration file // Test duplicate keys in invalid configuration file
auto cfg = getValidConfig(); auto cfg = getValidConfig();
@ -221,7 +338,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_duplicate) {
BOOST_CHECK(!config.isValid()); BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult(); const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE); BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE);
} }
BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) { BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) {
@ -237,7 +355,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) {
BOOST_CHECK(!config.isValid()); BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult(); const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDCONTENT);
BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 1); BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 1);
BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 0); BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 0);
@ -248,7 +367,7 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) {
BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) { BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) {
// Test wrong data type // Test wrong data type
auto cfg = getValidConfig(); auto cfg = getValidConfig();
cfg["input"]["invert_y"]="d"; cfg["input"]["invert_y"] = "d";
TempFile tempFile; TempFile tempFile;
tempFile.append(cfg); tempFile.append(cfg);
@ -258,7 +377,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) {
BOOST_CHECK(!config.isValid()); BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult(); const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDCONTENT);
BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 0); BOOST_CHECK_EQUAL(parseResult.getKeysRequiredMissing().size(), 0);
BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 1); BOOST_CHECK_EQUAL(parseResult.getKeysInvalidData().size(), 1);
@ -268,7 +388,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) {
BOOST_AUTO_TEST_CASE(test_config_invalid_empty) { BOOST_AUTO_TEST_CASE(test_config_invalid_empty) {
// Test reading empty configuration file // Test reading empty configuration file
// An empty file has a valid data structure, but has missing keys and is thus invalid. // An empty file has a valid data structure, but has missing keys and is
// thus invalid.
TempFile tempFile; TempFile tempFile;
tempFile.touch(); tempFile.touch();
BOOST_CHECK(tempFile.exists()); BOOST_CHECK(tempFile.exists());
@ -278,7 +399,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_empty) {
BOOST_CHECK(!config.isValid()); BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult(); const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDCONTENT); BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDCONTENT);
BOOST_CHECK_GE(parseResult.getKeysRequiredMissing().size(), 1); BOOST_CHECK_GE(parseResult.getKeysRequiredMissing().size(), 1);
} }
@ -292,7 +414,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_nonexisting) {
BOOST_CHECK(!config.isValid()); BOOST_CHECK(!config.isValid());
const auto &parseResult = config.getParseResult(); const auto &parseResult = config.getParseResult();
BOOST_CHECK_EQUAL(parseResult.type(), GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE); BOOST_CHECK_EQUAL(parseResult.type(),
GameConfig::ParseResult::ErrorType::INVALIDINPUTFILE);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()