diff --git a/src/config/private/config.cpp b/src/config/private/config.cpp index 45fcf27..5eb3af6 100644 --- a/src/config/private/config.cpp +++ b/src/config/private/config.cpp @@ -29,3 +29,14 @@ bool Config::loadConfig(const std::string& configFile) { return true; } +bool Config::isKeyInCache(const std::string &key) { + return configMap.find(key) != configMap.end(); +} + +void Config::addToCache(const std::string &key, const YAML::Node &node) { + configMap[key] = node; +} + +void Config::registerUnknownKey(const std::string &key) { + unknownKeys.push_back(key); +} diff --git a/src/config/public/config.h b/src/config/public/config.h index c79e841..6156299 100644 --- a/src/config/public/config.h +++ b/src/config/public/config.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "yaml-cpp/yaml.h" @@ -15,77 +16,154 @@ * @brief Singleton class to manage configuration settings loaded from a YAML file. */ class Config { - private: - /** - * @brief Private constructor to prevent instantiation. - */ - Config(); +private: + /** + * @brief Private constructor to prevent instantiation. + */ + Config(); - /** - * @brief Destructor. - */ - ~Config(); + /** + * @brief Destructor. + */ + ~Config(); - YAML::Node yamlRoot; ///< Root node of the YAML configuration. - std::string configFilePath; ///< Path to the configuration file. + YAML::Node yamlRoot; ///< Root node of the YAML configuration. + std::string configFilePath; ///< Path to the configuration file. + bool debug = false; ///< Flag to enable debug output. - public: - /** - * @brief Get the singleton instance of the Config class. - * @return Reference to the Config instance. - */ - static Config& getInstance(); + std::map configMap; ///< Cache for the location of configuration settings. + std::vector unknownKeys; ///< Cache for the existence of configuration settings. - Config (const Config&) = delete; - Config& operator= (const Config&) = delete; - Config (Config&&) = delete; - Config& operator= (Config&&) = delete; - - /** - * @brief Load configuration from a YAML file. - * @param configFilePath Path to the YAML configuration file. - * @return True if the configuration was loaded successfully, false otherwise. - */ - bool loadConfig(const std::string& configFilePath); - - /** - * @brief Get the input table from the configuration. - * @return Input table as a string. - */ - std::string getInputTable() const; - - /** - * @brief Get a configuration value by key. - * @tparam T Type of the value to retrieve. - * @param key Key of the configuration value. - * @param defaultValue Default value to return if the key does not exist. - * @return Configuration value of type T. - * - * @example - * @code - * Config& config = Config::getInstance(); - * config.loadConfig("example.yaml"); - * int maxIter = config.get("opac:lowTemp:numeric:maxIter", 10); - */ + /** + * @brief Get a value from the configuration cache. + * @tparam T Type of the value to retrieve. + * @param key Key of the configuration value. + * @param defaultValue Default value to return if the key does not exist. + * @return Configuration value of type T. + */ template - T get(const std::string &key, T defaultValue) { - YAML::Node node = YAML::Clone(yamlRoot); - std::istringstream keyStream(key); - std::string subKey; - while (std::getline(keyStream, subKey, ':')) { - if (!node[subKey]) { + T getFromCache(const std::string &key, T defaultValue) { + if (configMap.find(key) != configMap.end()) { + try { + return configMap[key].as(); + } catch (const YAML::Exception& e) { return defaultValue; } - node = node[subKey]; // go deeper + } + return defaultValue; + } + + + /** + * @brief Check if a key exists in the configuration cache. + * @param key Key to check. + * @return True if the key exists in the cache, false otherwise. + */ + bool isKeyInCache(const std::string &key); + + /** + * @brief Add a key-value pair to the configuration cache. + * @param key Key of the configuration value. + * @param node YAML node containing the configuration value. + */ + void addToCache(const std::string &key, const YAML::Node &node); + + /** + * @brief Register a key as not found in the configuration. + * @param key Key that was not found. + */ + void registerUnknownKey(const std::string &key); + +public: + /** + * @brief Get the singleton instance of the Config class. + * @return Reference to the Config instance. + */ + static Config& getInstance(); + + Config (const Config&) = delete; + Config& operator= (const Config&) = delete; + Config (Config&&) = delete; + Config& operator= (Config&&) = delete; + + void setDebug(bool debug) { this->debug = debug; } + + /** + * @brief Load configuration from a YAML file. + * @param configFilePath Path to the YAML configuration file. + * @return True if the configuration was loaded successfully, false otherwise. + */ + bool loadConfig(const std::string& configFilePath); + + /** + * @brief Get the input table from the configuration. + * @return Input table as a string. + */ + std::string getInputTable() const; + + /** + * @brief Get a configuration value by key. + * @tparam T Type of the value to retrieve. + * @param key Key of the configuration value. + * @param defaultValue Default value to return if the key does not exist. + * @return Configuration value of type T. + * + * @example + * @code + * Config& config = Config::getInstance(); + * config.loadConfig("example.yaml"); + * int maxIter = config.get("opac:lowTemp:numeric:maxIter", 10); + */ + template + T get(const std::string &key, T defaultValue) { + // --- Check if the key has already been checked for existence + if (std::find(unknownKeys.begin(), unknownKeys.end(), key) != unknownKeys.end()) { + return defaultValue; // If the key has already been added to the unknown cache do not traverse the YAML tree or hit the cache } - try { - return node.as(); - } catch (const YAML::Exception& e) { - return defaultValue; // return default value if the key does not exist + // --- Check if the key is already in the cache (avoid traversing YAML nodes) + if (isKeyInCache(key)) { + return getFromCache(key, defaultValue); + } + // --- If the key is not in the cache, check the YAML file + else { + YAML::Node node = YAML::Clone(yamlRoot); + std::istringstream keyStream(key); + std::string subKey; + while (std::getline(keyStream, subKey, ':')) { + if (!node[subKey]) { + // Key does not exist + registerUnknownKey(key); + return defaultValue; + } + node = node[subKey]; // go deeper + } + + try { + // Key exists and is of the requested type + addToCache(key, node); + return node.as(); + } catch (const YAML::Exception& e) { + // Key is not of the requested type + registerUnknownKey(key); + return defaultValue; // return default value if the key does not exist + } } } + friend std::ostream& operator<<(std::ostream& os, const Config& config) { + if (!config.debug) { + os << "Config file: " << config.configFilePath << std::endl; + } else{ + // Print entire YAML file from root + os << "Config file: " << config.configFilePath << std::endl; + os << config.yamlRoot << std::endl; + } + return os; + } + + // Setup gTest class as a friend + friend class configTestPrivateAccessor; }; #endif \ No newline at end of file diff --git a/tests/config/configTest.cpp b/tests/config/configTest.cpp index 817ead7..717ce99 100644 --- a/tests/config/configTest.cpp +++ b/tests/config/configTest.cpp @@ -5,6 +5,7 @@ #include #include #include +#include std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/config/example.yaml"; /** @@ -12,6 +13,32 @@ std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/test * @brief Unit tests for the Config class. */ +class configTestPrivateAccessor { +public: + static bool callIsKeyInCache(Config& config, const std::string& key) { + return config.isKeyInCache(key); + } + + static int callCacheSize(Config& config) { + return config.configMap.size(); + } + + static void callAddToCache(Config& config, const std::string& key, const YAML::Node& node) { + config.addToCache(key, node); + } + + static void callRegisterKeyNotFound(Config& config, const std::string& key) { + config.registerUnknownKey(key); + } + + static bool CheckIfKeyUnknown(Config& config, const std::string& key) { + if (std::find(config.unknownKeys.begin(), config.unknownKeys.end(), key) == config.unknownKeys.end()) { + return false; + } + return true; + } +}; + /** * @brief Test suite for the Config class. */ @@ -54,7 +81,31 @@ TEST_F(configTest, getTest) { EXPECT_EQ(polytropicIndex2, 2.0); } -TEST_F(configTest, secondSingleton) { +TEST_F(configTest, secondSingletonTest) { Config& config = Config::getInstance(); EXPECT_EQ(config.get("opac:lowTemp:numeric:maxIter", 10), 100); } + +TEST_F(configTest, isKeyInCacheTest) { + Config& config = Config::getInstance(); + config.loadConfig(EXAMPLE_FILENAME); + EXPECT_TRUE(configTestPrivateAccessor::callIsKeyInCache(config, "opac:lowTemp:numeric:maxIter")); + EXPECT_FALSE(configTestPrivateAccessor::callIsKeyInCache(config, "opac:lowTemp:numeric:maxIter2")); +} + +TEST_F(configTest, cacheSize) { + Config& config = Config::getInstance(); + config.loadConfig(EXAMPLE_FILENAME); + EXPECT_EQ(configTestPrivateAccessor::callCacheSize(config), 3); + EXPECT_NE(configTestPrivateAccessor::callCacheSize(config), 4); + config.get("outputDir", "DEBUG"); + EXPECT_EQ(configTestPrivateAccessor::callCacheSize(config), 4); +} + +TEST_F(configTest, unknownKeyTest) { + Config& config = Config::getInstance(); + config.loadConfig(EXAMPLE_FILENAME); + config.get("opac:lowTemp:numeric:random", 10); + EXPECT_FALSE(configTestPrivateAccessor::CheckIfKeyUnknown(config, "opac:lowTemp:numeric:maxIter")); + EXPECT_TRUE(configTestPrivateAccessor::CheckIfKeyUnknown(config, "opac:lowTemp:numeric:random")); +}