mirror of
https://github.com/rwengine/openrw.git
synced 2024-09-03 00:59:47 +02:00
config: unknown data will be kept in memory and saved onto disk
This commit is contained in:
parent
7a86c199b0
commit
a92f24cbb4
@ -1,16 +1,16 @@
|
||||
#include "GameConfig.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
#include <rw/defines.hpp>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
const std::string kConfigDirectoryName("OpenRW");
|
||||
|
||||
GameConfig::GameConfig(const std::string& configName,
|
||||
const std::string& configPath)
|
||||
GameConfig::GameConfig(const std::string &configName,
|
||||
const std::string &configPath)
|
||||
: m_configName(configName)
|
||||
, m_configPath(configPath)
|
||||
, m_parseResult()
|
||||
@ -23,7 +23,8 @@ GameConfig::GameConfig(const std::string& configName,
|
||||
auto configFile = getConfigFile();
|
||||
|
||||
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 {
|
||||
@ -41,17 +42,17 @@ const GameConfig::ParseResult &GameConfig::getParseResult() const {
|
||||
std::string GameConfig::getDefaultConfigPath() {
|
||||
#if defined(RW_LINUX) || defined(RW_FREEBSD) || defined(RW_NETBSD) || \
|
||||
defined(RW_OPENBSD)
|
||||
char* config_home = getenv("XDG_CONFIG_HOME");
|
||||
char *config_home = getenv("XDG_CONFIG_HOME");
|
||||
if (config_home != nullptr) {
|
||||
return std::string(config_home) + "/" + kConfigDirectoryName;
|
||||
}
|
||||
char* home = getenv("HOME");
|
||||
char *home = getenv("HOME");
|
||||
if (home != nullptr) {
|
||||
return std::string(home) + "/.config/" + kConfigDirectoryName;
|
||||
}
|
||||
|
||||
#elif defined(RW_OSX)
|
||||
char* home = getenv("HOME");
|
||||
char *home = getenv("HOME");
|
||||
if (home)
|
||||
return std::string(home) + "/Library/Preferences/" +
|
||||
kConfigDirectoryName;
|
||||
@ -67,7 +68,7 @@ std::string GameConfig::getDefaultConfigPath() {
|
||||
|
||||
std::string stripComments(const std::string &str) {
|
||||
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 {
|
||||
@ -83,7 +84,7 @@ struct StringTranslator {
|
||||
|
||||
struct BoolTranslator {
|
||||
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> res;
|
||||
try {
|
||||
@ -99,7 +100,7 @@ struct BoolTranslator {
|
||||
|
||||
struct IntTranslator {
|
||||
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> res;
|
||||
try {
|
||||
@ -115,8 +116,7 @@ struct IntTranslator {
|
||||
|
||||
GameConfig::ParseResult GameConfig::saveConfig() {
|
||||
auto filename = getConfigFile();
|
||||
return parseConfig(ParseType::CONFIG, "",
|
||||
ParseType::FILE, filename);
|
||||
return parseConfig(ParseType::CONFIG, "", ParseType::FILE, filename);
|
||||
}
|
||||
|
||||
std::string GameConfig::getDefaultINIString() {
|
||||
@ -125,10 +125,11 @@ std::string GameConfig::getDefaultINIString() {
|
||||
return result;
|
||||
}
|
||||
|
||||
GameConfig::ParseResult GameConfig::parseConfig(
|
||||
GameConfig::ParseType srcType, const std::string &source,
|
||||
ParseType destType, std::string &destination)
|
||||
{
|
||||
GameConfig::ParseResult GameConfig::parseConfig(GameConfig::ParseType srcType,
|
||||
const std::string &source,
|
||||
ParseType destType,
|
||||
std::string &destination) {
|
||||
// srcTree: holds all key/value pairs
|
||||
pt::ptree srcTree;
|
||||
ParseResult parseResult(srcType, source, destType, destination);
|
||||
|
||||
@ -152,12 +153,16 @@ GameConfig::ParseResult GameConfig::parseConfig(
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
// knownKeys: holds all known keys
|
||||
std::vector<std::string> knownKeys;
|
||||
|
||||
auto read_config = [&](const std::string &key, auto &target,
|
||||
const auto &defaultValue, auto &translator,
|
||||
bool optional=true) {
|
||||
const auto &defaultValue, auto &translator,
|
||||
bool optional = true) {
|
||||
typedef typename std::remove_reference<decltype(target)>::type config_t;
|
||||
|
||||
config_t sourceValue;
|
||||
knownKeys.push_back(key);
|
||||
|
||||
switch (srcType) {
|
||||
case ParseType::DEFAULT:
|
||||
@ -210,13 +215,50 @@ GameConfig::ParseResult GameConfig::parseConfig(
|
||||
// Additionally, add them to the unit test.
|
||||
|
||||
// @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("input.invert_y", this->m_inputInvertY, false, boolt);
|
||||
|
||||
if (!parseResult.isValid())
|
||||
return parseResult;
|
||||
if (!parseResult.isValid()) return parseResult;
|
||||
|
||||
// Build the unknown key/value map from the correct source
|
||||
switch (srcType) {
|
||||
case ParseType::FILE:
|
||||
case ParseType::STRING:
|
||||
for (const auto §ion : 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 {
|
||||
if (destType == ParseType::STRING) {
|
||||
@ -234,8 +276,8 @@ GameConfig::ParseResult GameConfig::parseConfig(
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
std::string GameConfig::extractFilenameParseTypeData(ParseType type, const std::string &data)
|
||||
{
|
||||
std::string GameConfig::extractFilenameParseTypeData(ParseType type,
|
||||
const std::string &data) {
|
||||
switch (type) {
|
||||
case ParseType::CONFIG:
|
||||
return "<configuration>";
|
||||
@ -249,16 +291,19 @@ std::string GameConfig::extractFilenameParseTypeData(ParseType type, const std::
|
||||
}
|
||||
}
|
||||
|
||||
GameConfig::ParseResult::ParseResult(
|
||||
GameConfig::ParseType srcType, const std::string &source,
|
||||
GameConfig::ParseType destType, const std::string &destination)
|
||||
GameConfig::ParseResult::ParseResult(GameConfig::ParseType srcType,
|
||||
const std::string &source,
|
||||
GameConfig::ParseType destType,
|
||||
const std::string &destination)
|
||||
: m_result(ErrorType::GOOD)
|
||||
, m_inputfilename(GameConfig::extractFilenameParseTypeData(srcType, source))
|
||||
, m_outputfilename(GameConfig::extractFilenameParseTypeData(destType, destination))
|
||||
, m_outputfilename(
|
||||
GameConfig::extractFilenameParseTypeData(destType, destination))
|
||||
, m_line(0)
|
||||
, m_message()
|
||||
, m_keys_requiredMissing()
|
||||
, m_keys_invalidData() {
|
||||
, m_keys_invalidData()
|
||||
, m_unknownData() {
|
||||
}
|
||||
|
||||
GameConfig::ParseResult::ParseResult()
|
||||
@ -280,7 +325,7 @@ bool GameConfig::ParseResult::isValid() const {
|
||||
}
|
||||
|
||||
void GameConfig::ParseResult::failInputFile(size_t line,
|
||||
const std::string &message) {
|
||||
const std::string &message) {
|
||||
this->m_result = ParseResult::ErrorType::INVALIDINPUTFILE;
|
||||
this->m_line = line;
|
||||
this->m_message = message;
|
||||
@ -301,17 +346,19 @@ void GameConfig::ParseResult::failInvalidData(const std::string &key) {
|
||||
}
|
||||
|
||||
void GameConfig::ParseResult::failOutputFile(size_t line,
|
||||
const std::string &message) {
|
||||
const std::string &message) {
|
||||
this->m_result = ParseResult::ErrorType::INVALIDOUTPUTFILE;
|
||||
this->m_line = line;
|
||||
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;
|
||||
}
|
||||
|
||||
const std::vector<std::string> &GameConfig::ParseResult::getKeysInvalidData() const {
|
||||
const std::vector<std::string> &GameConfig::ParseResult::getKeysInvalidData()
|
||||
const {
|
||||
return this->m_keys_invalidData;
|
||||
}
|
||||
|
||||
@ -321,27 +368,23 @@ std::string GameConfig::ParseResult::what() const {
|
||||
return "Good";
|
||||
case ErrorType::INVALIDARGUMENT:
|
||||
return "Invalid argument: destination cannot be the default config";
|
||||
case ErrorType::INVALIDINPUTFILE:
|
||||
{
|
||||
case ErrorType::INVALIDINPUTFILE: {
|
||||
std::ostringstream oss;
|
||||
oss << "Error while reading \""
|
||||
<< this->m_inputfilename << "\":" << this->m_line << ":\n"
|
||||
oss << "Error while reading \"" << this->m_inputfilename
|
||||
<< "\":" << this->m_line << ":\n"
|
||||
<< this->m_message;
|
||||
return oss.str();
|
||||
}
|
||||
case ErrorType::INVALIDOUTPUTFILE:
|
||||
{
|
||||
case ErrorType::INVALIDOUTPUTFILE: {
|
||||
std::ostringstream oss;
|
||||
oss << "Error while writing \""
|
||||
<< this->m_inputfilename << "\":" << this->m_line << ":\n"
|
||||
oss << "Error while writing \"" << this->m_inputfilename
|
||||
<< "\":" << this->m_line << ":\n"
|
||||
<< this->m_message;
|
||||
return oss.str();
|
||||
}
|
||||
case ErrorType::INVALIDCONTENT:
|
||||
{
|
||||
case ErrorType::INVALIDCONTENT: {
|
||||
std::ostringstream oss;
|
||||
oss << "Error while parsing \""
|
||||
<< this->m_inputfilename << "\".";
|
||||
oss << "Error while parsing \"" << this->m_inputfilename << "\".";
|
||||
if (this->m_keys_requiredMissing.size()) {
|
||||
oss << "\nRequired keys that are missing:";
|
||||
for (auto &key : this->m_keys_requiredMissing) {
|
||||
@ -360,3 +403,18 @@ std::string GameConfig::ParseResult::what() const {
|
||||
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;
|
||||
}
|
||||
|
@ -1,47 +1,50 @@
|
||||
#ifndef RWGAME_GAMECONFIG_HPP
|
||||
#define RWGAME_GAMECONFIG_HPP
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class GameConfig {
|
||||
private:
|
||||
enum ParseType {
|
||||
DEFAULT,
|
||||
CONFIG,
|
||||
FILE,
|
||||
STRING
|
||||
};
|
||||
enum ParseType { DEFAULT, CONFIG, FILE, STRING };
|
||||
|
||||
/**
|
||||
* @brief extractFilenameParseTypeData Get a human readable filename string
|
||||
* @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:
|
||||
class ParseResult {
|
||||
public:
|
||||
enum ErrorType {
|
||||
/// GOOD: Input file/string was 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,
|
||||
/// INVALIDARGUMENT: The parser received impossible arguments
|
||||
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,
|
||||
/// 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
|
||||
};
|
||||
|
||||
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 source The source of the parser
|
||||
* @param destType Type of the destination
|
||||
* @param destination The destination
|
||||
*/
|
||||
ParseResult(ParseType srcType, const std::string &source,
|
||||
ParseType destType, const std::string &destination);
|
||||
ParseType destType, const std::string &destination);
|
||||
|
||||
/**
|
||||
* @brief ParseResult Create empty ParseResult
|
||||
@ -93,7 +96,8 @@ public:
|
||||
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 message Description of the error
|
||||
*/
|
||||
@ -110,6 +114,20 @@ public:
|
||||
* @return String with the error description
|
||||
*/
|
||||
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:
|
||||
/// Type of the failure
|
||||
ErrorType m_result;
|
||||
@ -120,7 +138,8 @@ public:
|
||||
/// Filename of the output file
|
||||
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;
|
||||
|
||||
/// Description of the failure (on invalid input or output file)
|
||||
@ -132,6 +151,15 @@ public:
|
||||
/// All keys that contain invalid data
|
||||
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;
|
||||
};
|
||||
|
||||
@ -140,8 +168,8 @@ public:
|
||||
* @param configName The configuration filename to load
|
||||
* @param configPath Where to look.
|
||||
*/
|
||||
GameConfig(const std::string& configName,
|
||||
const std::string& configPath = getDefaultConfigPath());
|
||||
GameConfig(const std::string &configName,
|
||||
const std::string &configPath = getDefaultConfigPath());
|
||||
|
||||
/**
|
||||
* @brief getFilePath Returns the system file path for the configuration
|
||||
@ -166,15 +194,16 @@ public:
|
||||
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
|
||||
*/
|
||||
std::string getDefaultINIString();
|
||||
|
||||
const std::string& getGameDataPath() const {
|
||||
const std::string &getGameDataPath() const {
|
||||
return m_gamePath;
|
||||
}
|
||||
const std::string& getGameLanguage() const {
|
||||
const std::string &getGameLanguage() const {
|
||||
return m_gameLanguage;
|
||||
}
|
||||
bool getInputInvertY() const {
|
||||
@ -186,6 +215,7 @@ private:
|
||||
|
||||
/**
|
||||
* @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 source don't care if srcType == (DEFAULT | CONFIG),
|
||||
* path of INI file if srcType == FILE
|
||||
@ -197,7 +227,7 @@ private:
|
||||
* @return True if the parsing succeeded
|
||||
*/
|
||||
ParseResult parseConfig(ParseType srcType, const std::string &source,
|
||||
ParseType destType, std::string &destination);
|
||||
ParseType destType, std::string &destination);
|
||||
|
||||
/* Config State */
|
||||
std::string m_configName;
|
||||
|
@ -3,12 +3,39 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "rw/defines.hpp"
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
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 §ion : 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 result;
|
||||
@ -16,14 +43,17 @@ simpleConfig_t getValidConfig() {
|
||||
// to test the robustness of the INI parser.
|
||||
// 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"]["\tlanguage\t "] = " american ;american english french german italian spanish.";
|
||||
result["input"]["invert_y"] = "1 #values != 0 enable input inversion. Optional.";
|
||||
result["game"]["\tlanguage\t "] =
|
||||
" american ;american english french german italian spanish.";
|
||||
result["input"]["invert_y"] =
|
||||
"1 #values != 0 enable input inversion. Optional.";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const simpleConfig_t &config) {
|
||||
for (auto §ion : config) {
|
||||
os << "[" << section.first << "]" << "\n";
|
||||
os << "[" << section.first << "]"
|
||||
<< "\n";
|
||||
for (auto &keyValue : section.second) {
|
||||
os << keyValue.first << "=" << keyValue.second << "\n";
|
||||
}
|
||||
@ -59,10 +89,11 @@ public:
|
||||
return this->m_path.parent_path().string();
|
||||
}
|
||||
void change_perms_readonly() {
|
||||
fs::permissions(this->m_path, fs::perms::owner_read
|
||||
| fs::perms::group_read | fs::perms::others_read);
|
||||
fs::permissions(this->m_path, fs::perms::owner_read |
|
||||
fs::perms::group_read |
|
||||
fs::perms::others_read);
|
||||
}
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
bool append(T t) {
|
||||
// Append argument at the end of the file.
|
||||
// File is open/closes repeatedly. Not optimal.
|
||||
@ -71,7 +102,7 @@ public:
|
||||
ofs.close();
|
||||
return ofs.good();
|
||||
}
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
bool write(T t) {
|
||||
// Write the argument to the file, discarding all contents.
|
||||
// File is open/closes repeatedly. Not optimal.
|
||||
@ -80,15 +111,31 @@ public:
|
||||
ofs.close();
|
||||
return ofs.good();
|
||||
}
|
||||
|
||||
private:
|
||||
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;
|
||||
};
|
||||
|
||||
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) {
|
||||
// Check the behavior of TempFile
|
||||
TempFile tempFile;
|
||||
@ -126,8 +173,10 @@ BOOST_AUTO_TEST_CASE(test_config_valid) {
|
||||
GameConfig config(tempFile.filename(), tempFile.dirname());
|
||||
|
||||
BOOST_CHECK(config.isValid());
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().type(),
|
||||
GameConfig::ParseResult::ErrorType::GOOD);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(),
|
||||
0);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0);
|
||||
|
||||
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());
|
||||
|
||||
BOOST_CHECK(config.isValid());
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().type(), GameConfig::ParseResult::ErrorType::GOOD);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(), 0);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().type(),
|
||||
GameConfig::ParseResult::ErrorType::GOOD);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysRequiredMissing().size(),
|
||||
0);
|
||||
BOOST_CHECK_EQUAL(config.getParseResult().getKeysInvalidData().size(), 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(config.getInputInvertY(), false);
|
||||
@ -176,6 +227,55 @@ BOOST_AUTO_TEST_CASE(test_config_save) {
|
||||
|
||||
GameConfig config2(tempFile.filename(), tempFile.dirname());
|
||||
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) {
|
||||
@ -191,7 +291,8 @@ BOOST_AUTO_TEST_CASE(test_config_save_readonly) {
|
||||
|
||||
auto writeResult = config.saveConfig();
|
||||
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) {
|
||||
@ -209,6 +310,22 @@ BOOST_AUTO_TEST_CASE(test_config_valid_default) {
|
||||
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) {
|
||||
// Test duplicate keys in invalid configuration file
|
||||
auto cfg = getValidConfig();
|
||||
@ -221,7 +338,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_duplicate) {
|
||||
|
||||
BOOST_CHECK(!config.isValid());
|
||||
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) {
|
||||
@ -237,7 +355,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_required_missing) {
|
||||
BOOST_CHECK(!config.isValid());
|
||||
|
||||
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.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) {
|
||||
// Test wrong data type
|
||||
auto cfg = getValidConfig();
|
||||
cfg["input"]["invert_y"]="d";
|
||||
cfg["input"]["invert_y"] = "d";
|
||||
|
||||
TempFile tempFile;
|
||||
tempFile.append(cfg);
|
||||
@ -258,7 +377,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) {
|
||||
BOOST_CHECK(!config.isValid());
|
||||
|
||||
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.getKeysInvalidData().size(), 1);
|
||||
@ -268,7 +388,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_wrong_type) {
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_config_invalid_empty) {
|
||||
// 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.touch();
|
||||
BOOST_CHECK(tempFile.exists());
|
||||
@ -278,7 +399,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_empty) {
|
||||
BOOST_CHECK(!config.isValid());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -292,7 +414,8 @@ BOOST_AUTO_TEST_CASE(test_config_invalid_nonexisting) {
|
||||
BOOST_CHECK(!config.isValid());
|
||||
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user