feat(TOML & Glaze): YAML -> TOML

YAML is a horrid format with far too many edge cases. We have switched
to TOML. Further, we have completly reworked the framework so that

1. There is no longer any global config state. Config objects now must
be passed between scopes by the caller. This will introduce some more
friction but whill also make order of initialization clear
2. Config objects are now strongly typed and there is a single sourth of
truth for any given config object baked in using the some struct.
This commit is contained in:
2025-12-05 14:26:22 -05:00
parent 1a7f4837c6
commit 05b7b94c83
25 changed files with 561 additions and 454 deletions

1
.gitignore vendored
View File

@@ -71,6 +71,7 @@ subprojects/qhull/
subprojects/libconstants/
subprojects/liblogging/
subprojects/packagecache/
subprojects/glaze/
qhull.wrap

View File

@@ -0,0 +1,16 @@
glaze_cmake_options = cmake.subproject_options()
glaze_cmake_options.add_cmake_defines({
'BUILD_SHARED_LIBS': 'OFF',
'BUILD_STATIC_LIBS': 'ON',
'CMAKE_INSTALL_LIBDIR': get_option('libdir'),
'CMAKE_INSTALL_INCLUDEDIR': get_option('includedir'),
'CMAKE_POSITION_INDEPENDENT_CODE': 'ON',
'galze_BUILD_EXAMPLES': 'OFF',
})
glaze_sp = cmake.subproject(
'glaze',
options: glaze_cmake_options,
)
glaze_dep = glaze_sp.dependency('glaze_glaze')

View File

@@ -1,2 +1,2 @@
cmake = import('cmake')
subdir('yaml-cpp')
subdir('glaze')

View File

@@ -1,42 +0,0 @@
cmake = import('cmake')
yaml_cpp_cmake_options = cmake.subproject_options()
yaml_cpp_cmake_options.add_cmake_defines({
'BUILD_SHARED_LIBS': 'OFF',
'BUILD_STATIC_LIBS': 'ON',
'YAML_CPP_BUILD_TESTS': 'OFF',
'CMAKE_CXX_FLAGS': '-Wno-shadow',
'CMAKE_C_FLAGS': '-Wno-shadow',
'CMAKE_INSTALL_LIBDIR': get_option('libdir'),
'CMAKE_INSTALL_INCLUDEDIR': get_option('includedir'),
'CMAKE_POLICY_VERSION_MINIMUM': '3.5',
'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'
})
yaml_cpp_sp = cmake.subproject(
'yaml-cpp',
options: yaml_cpp_cmake_options,
)
yaml_cpp_tgt = yaml_cpp_sp.target('yaml-cpp')
yaml_cpp_inc = yaml_cpp_sp.include_directories('yaml-cpp')
empty_yaml_cpp_file = configure_file(
output: 'yaml_cpp_dummy_ar.cpp',
command: ['echo'],
capture: true
)
libyaml_static = static_library(
'yaml_cpp-static',
empty_yaml_cpp_file,
objects: [yaml_cpp_tgt.extract_all_objects(recursive: true)],
include_directories: yaml_cpp_inc,
pic: true,
install: false
)
yaml_cpp_dep = declare_dependency(
link_with: libyaml_static,
include_directories: yaml_cpp_inc,
)

View File

@@ -0,0 +1,4 @@
[main]
x = 10
y = 20
person = {age = 30, name = "Alice"}

View File

@@ -1,4 +0,0 @@
men: ["John Smith", "Bill Jones"]
women:
- Karol Boudreaux
- Emily Boudreaux

View File

@@ -1,35 +1,46 @@
#include "fourdst/config/config.h"
#include "glaze/glaze.hpp"
#include <string>
#include <vector>
#include <iostream>
#include <filesystem>
std::string get_validated_filename(int argc, char* argv[]) {
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " <input_file>" << std::endl;
exit(1);
}
using namespace fourdst::config;
std::filesystem::path const file_path{ argv[1] };
struct sub {
double x;
double y;
};
if (not std::filesystem::exists(file_path) || not std::filesystem::is_regular_file(file_path)) {
std::cout << "Error: File does not exist or is not a regular file." << std::endl;
exit(1);
}
struct BoundaryConditions {
double pressure = 1e6;
sub sub;
};
return file_path.string();
}
struct ExampleConfig {
double parameterA = 1.0;
int parameterB = 1.0;
std::string parameterC = "default_value";
std::vector<double> parameterD = {0.1, 0.2, 0.3};
BoundaryConditions boundaryConditions;
};
int main(int argc, char* argv[]) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
struct Person {
int age;
std::string name;
};
std::string filename = get_validated_filename(argc, argv);
config.loadConfig(filename);
struct AppConfig {
double x;
double y;
Person person;
};
auto men = config.get<std::vector<std::string>>("men", {});
int main() {
const Config<ExampleConfig> cfg;
cfg.save();
cfg.save_schema(".");
for (const auto& name : men) {
std::cout << "men are " << name << std::endl;
}
}
Config<AppConfig> loaded;
loaded.save_schema(".");
loaded.load("config_example.toml");
std::println("{}", loaded);
}

View File

@@ -2,18 +2,22 @@
#include <string>
#include <vector>
#include <iostream>
#include <filesystem>
struct AppConfig {
double x;
double y;
std::vector<std::string> men;
};
int main(int argc, char* argv[]) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
fourdst::config::Config<AppConfig> config;
config.load("/input.toml");
config.loadConfig("/input.yaml");
auto x = config->x;
const auto& men = config->men;
auto men = config.get<std::vector<std::string>>("men", {});
for (const auto& name : men) {
std::cout << "men are " << name << std::endl;
}
for (const auto& name : men) {
std::cout << "men are " << name << std::endl;
}
}

View File

@@ -35,16 +35,16 @@ if get_option('build_examples')
subdir('examples')
endif
if get_option('pkg_config')
message('Generating pkg-config file for libconfig...')
pkg = import('pkgconfig')
pkg.generate(
name: 'libconfig',
description: 'Configuration module for SERiF and related projects',
version: meson.project_version(),
libraries: [libconfig],
subdirs: ['fourdst'],
filebase: 'fourdst_config',
install_dir: join_paths(get_option('libdir'), 'pkgconfig')
)
endif
#if get_option('pkg_config')
# message('Generating pkg-config file for libconfig...')
# pkg = import('pkgconfig')
# pkg.generate(
# name: 'libconfig',
# description: 'Configuration module for SERiF and related projects',
# version: meson.project_version(),
# libraries: [libconfig],
# subdirs: ['fourdst'],
# filebase: 'fourdst_config',
# install_dir: join_paths(get_option('libdir'), 'pkgconfig')
# )
#endif

View File

@@ -0,0 +1,166 @@
#pragma once
#include <filesystem>
#include "glaze/toml.hpp"
#include <string>
#include <map>
#include <optional>
#include "fourdst/config/registry.h"
#include "fourdst/config/exceptions/exceptions.h"
namespace fourdst::config::utils {
inline std::string extract_error_key(const std::string& buffer, const glz::error_ctx& pe) {
if (pe.location >= buffer.size()) return "unknown";
size_t line_start = pe.location;
while (line_start > 0 && buffer[line_start - 1] != '\n') {
line_start--;
}
size_t line_end = pe.location;
while (line_end < buffer.size() && buffer[line_end] != '\n' && buffer[line_end] != '\r') {
line_end++;
}
std::string line = buffer.substr(line_start, line_end - line_start);
const size_t separator_pos = line.find_first_of("=:");
if (separator_pos != std::string::npos) {
std::string key_part = line.substr(0, separator_pos);
while (!key_part.empty() && std::isspace(key_part.back())) {
key_part.pop_back();
}
size_t first_char = 0;
while (first_char < key_part.size() && std::isspace(key_part[first_char])) {
first_char++;
}
if (first_char < key_part.size()) {
return key_part.substr(first_char);
}
}
return line;
}
}
namespace fourdst::config {
enum class ConfigState {
DEFAULT,
LOADED_FROM_FILE
};
template <typename T>
class Config {
public:
Config() {
(void)m_registrar;
}
const T* operator->() const { return &m_content.main; }
const T& main() const { return m_content.main; }
void save(std::optional<std::string_view> path = std::nullopt) const {
if (!path) {
path = std::string(glz::name_v<T>) + ".toml";
}
auto err = glz::write_file_toml(m_content, path.value(), std::string{});
if (err) {
throw exceptions::ConfigSaveError(
std::format(
"Config::save: Failed to save config to {} with glaze error {} ",
std::string(path.value()),
glz::format_error(err.ec)
)
);
}
}
void load(const std::string_view path) {
std::string buffer;
const auto ec = glz::file_to_buffer(buffer, path);
if (ec != glz::error_code::none) {
throw exceptions::ConfigLoadError(
std::format(
"Config::load: Failed to load config from {} with glaze error {} ",
std::string(path),
glz::format_error(ec)
)
);
}
auto err = glz::read<
glz::opts{
.format = glz::TOML,
.error_on_unknown_keys = true
}>(m_content, buffer);
if (err) {
throw exceptions::ConfigParseError(
std::format(
"Config::load: Failed to parse config from {} with glaze error {} (Key: {}) ",
std::string(path),
glz::format_error(err.ec),
utils::extract_error_key(buffer, err)
)
);
}
m_state = ConfigState::LOADED_FROM_FILE;
}
void save_schema(const std::string_view dir) const {
Registry::generate_named(dir, std::string(glz::name_v<T>));
}
ConfigState get_state() const { return m_state; }
std::string describe_state() const {
switch (m_state) {
case ConfigState::DEFAULT:
return "DEFAULT";
case ConfigState::LOADED_FROM_FILE:
return "LOADED_FROM_FILE";
default:
return "UNKNOWN";
}
}
private:
struct Content {
T main;
};
Content m_content;
ConfigState m_state = ConfigState::DEFAULT;
struct Registrar {
Registrar() {
const auto name = std::string(glz::name_v<T>);
Registry::register_schema<Content>(name);
}
};
static inline Registrar m_registrar;
};
}
template <typename T, typename CharT>
struct std::formatter<fourdst::config::Config<T>, CharT> {
static constexpr auto parse(auto& ctx) { return ctx.begin(); }
auto format(const fourdst::config::Config<T>& config, auto& ctx) const {
const T& inner_value = config.main();
struct Content {
T main;
};
Content content{inner_value};
std::string buffer;
const glz::error_ctx ec = glz::write<glz::opts{.format=glz::TOML, .prettify = true}>(content, buffer);
if (ec) {
return std::format_to(ctx.out(), "Error serializing config");
}
return std::format_to(ctx.out(), "{}", buffer);
}
};

View File

@@ -20,227 +20,6 @@
// *********************************************************************** */
#pragma once
#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <stdexcept>
#include "fourdst/config/base.h"
#include "fourdst/config/exceptions/exceptions.h"
// Required for YAML parsing
#include "yaml-cpp/yaml.h"
// -- Forward Def of Resource manager to let it act as a friend of Config --
// Note this is for SERiF development
namespace serif::resource { class ResourceManager; }
class configTestPrivateAccessor; // Forward declaration for test utility
namespace fourdst::config {
/**
* @class Config
* @brief Singleton class to manage configuration settings loaded from a YAML file.
*/
class Config {
private:
/**
* @brief Private constructor to prevent instantiation.
*/
Config();
/**
* @brief Destructor.
*/
~Config();
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.
bool loaded = false; ///< Flag to indicate if the configuration has been loaded.
std::map<std::string, YAML::Node> configMap; ///< Cache for the location of configuration settings.
std::vector<std::string> unknownKeys; ///< Cache for the existence of configuration settings.
/**
* @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 <typename T>
T getFromCache(const std::string &key, T defaultValue) {
if (configMap.find(key) != configMap.end()) {
try {
return configMap[key].as<T>();
} catch (const YAML::Exception& e) {
return defaultValue;
}
}
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);
bool m_loaded = false;
// Only friends can access get without a default value
template <typename T>
T get(const std::string &key) {
if (!m_loaded) {
throw std::runtime_error("Error! Config file not loaded");
}
if (has(key)) {
return getFromCache<T>(key, T());
} else {
throw std::runtime_error("Error! Key not found in config file");
}
}
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<int>("opac:lowTemp:numeric:maxIter", 10);
*/
template <typename T>
T get(const std::string &key, T defaultValue) {
if (!m_loaded) {
// ONLY THROW ERROR IF HARSH OR WARN CONFIGURATION
#if defined(CONFIG_HARSH)
throw std::runtime_error("Error! Config file not loaded. To disable this error, recompile with CONFIG_HARSH=0");
#elif defined(CONFIG_WARN)
std::cerr << "Warning! Config file not loaded. This instance of 4DSSE was compiled with CONFIG_WARN so the code will continue using only default values" << std::endl;
#endif
}
// --- 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
}
// --- Check if the key is already in the cache (avoid traversing YAML nodes)
if (isKeyInCache(key)) {
return getFromCache<T>(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<T>();
} 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
}
}
}
/**
* @brief Check if the key exists in the given config file
* @param key Key to check;
* @return boolean true or false
*/
bool has(const std::string &key);
/**
* @brief Get all keys defined in the configuration file.
* @return Vector of all keys in the configuration file.
*/
std::vector<std::string> keys() const;
/**
* @brief Print the configuration file path and the YAML root node.
* @param os Output stream.
* @param config Config object to print.
* @return Output stream.
*/
friend std::ostream& operator<<(std::ostream& os, const Config& config) {
if (!config.m_loaded) {
os << "Config file not loaded" << std::endl;
return os;
}
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; // Friend declaration for global test accessor
// -- Resource Manager is a friend of config so it can create a seperate instance
friend class serif::resource::ResourceManager; // Adjusted friend declaration
};
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <stdexcept>
#include <string>
namespace fourdst::config::exceptions {
class ConfigError : public std::exception {
public:
ConfigError(const std::string & what): m_msg(what) {}
const char* what() const noexcept override {
return m_msg.c_str();
}
private:
std::string m_msg;
};
class ConfigSaveError final : public ConfigError {
using ConfigError::ConfigError;
};
class ConfigLoadError final : public ConfigError {
using ConfigError::ConfigError;
};
class ConfigParseError final : public ConfigError {
using ConfigError::ConfigError;
};
class SchemaGenerationError final : public ConfigError {
using ConfigError::ConfigError;
};
class SchemaNameError final : public ConfigError {
using ConfigError::ConfigError;
};
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include <vector>
#include <functional>
#include <filesystem>
#include <string>
#include <glaze/glaze.hpp>
#include <exception>
#include "fourdst/config/exceptions/exceptions.h"
namespace fourdst::config {
struct Registry {
using SchemaWriter = std::function<void(std::filesystem::path)>;
static std::unordered_map<std::string, SchemaWriter>& get_writers() {
static std::unordered_map<std::string, SchemaWriter> writers;
return writers;
}
template <typename T>
static void register_schema(const std::string& name) {
auto& writers = get_writers();
writers.insert({name, [name](const std::filesystem::path &dir) {
auto schema_r = glz::write_json_schema<T>();
if (!schema_r.has_value()) {
throw std::runtime_error("Failed to generate schema for " + name);
}
std::string schema = schema_r.value();
const auto path = dir / (name + ".schema.json");
const auto err = glz::buffer_to_file(schema, path.string());
if (err != glz::error_code::none) {
throw exceptions::SchemaGenerationError("Failed to write schema for " + name + " to " + path.string());
}
}});
}
static void generate_all(const std::filesystem::path& dir) {
std::filesystem::create_directories(dir);
for (const auto &writer: get_writers() | std::views::values) {
writer(dir);
}
}
static void generate_named(const std::filesystem::path& dir, const std::string& name) {
const auto& writers = get_writers();
const auto it = writers.find(name);
if (it == writers.end()) {
throw exceptions::SchemaNameError("No schema registered with name: " + name);
}
it->second(dir);
}
};
}

View File

@@ -5,22 +5,23 @@ config_sources = files(
# Define the libconfig library so it can be linked against by other parts of the build system
libconfig = library('config',
config_sources,
include_directories: include_directories('include'),
cpp_args: ['-fvisibility=default'],
dependencies: [yaml_cpp_dep],
install : true)
#libconfig = library('config',
# config_sources,
# include_directories: include_directories('include'),
# cpp_args: ['-fvisibility=default'],
# dependencies: [yaml_cpp_dep, glaze_dep],
# install : true)
config_dep = declare_dependency(
include_directories: include_directories('include'),
link_with: libconfig,
sources: config_sources,
dependencies: [yaml_cpp_dep],
# link_with: libconfig,
# sources: config_sources,
dependencies: [glaze_dep],
)
# Make headers accessible
config_headers = files(
'include/fourdst/config/config.h'
'include/fourdst/config/config.h',
'include/fourdst/config/registry.h',
)
install_headers(config_headers, subdir : 'fourdst/fourdst/config')

5
subprojects/glaze.wrap Normal file
View File

@@ -0,0 +1,5 @@
[wrap-git]
url = https://github.com/stephenberry/glaze.git
revision = v6.1.0
[cmake]

View File

@@ -1,9 +0,0 @@
--- yaml-cpp/src/emitterutils.cpp.bak 2025-07-29 08:42:40
+++ yaml-cpp/src/emitterutils.cpp 2025-07-29 08:42:50
@@ -1,5 +1,6 @@
#include <algorithm>
#include <iomanip>
+#include <cstdint>
#include <sstream>
#include "emitterutils.h"

View File

@@ -1,10 +0,0 @@
--- yaml-cpp/CMakeLists.txt.orig 2025-07-24 08:18:01
+++ yaml-cpp/CMakeLists.txt 2025-07-24 08:18:25
@@ -93,7 +93,6 @@
target_compile_options(yaml-cpp
PRIVATE
- $<${not-msvc}:-Wall -Wextra -Wshadow -Weffc++ -Wno-long-long>
$<${not-msvc}:-pedantic -pedantic-errors>
$<$<AND:${backport-msvc-runtime},${msvc-rt-mtd-static}>:-MTd>

View File

@@ -1,6 +0,0 @@
[wrap-git]
url = https://github.com/jbeder/yaml-cpp.git
revision = yaml-cpp-0.7.0
diff_files = yaml-cpp/disableShadowWarnings.patch, yaml-cpp/addCSTDINTToEmitterUtils.patch
[cmake]

View File

@@ -7,38 +7,46 @@
#include <algorithm>
#include "fourdst/config/config.h"
#include "test_schema.h"
std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/config/example.yaml";
std::string get_good_example_file() {
const char* source_root = getenv("MESON_SOURCE_ROOT");
if (source_root == nullptr) {
throw std::runtime_error("MESON_SOURCE_ROOT environment variable is not set.");
}
return std::string(source_root) + "/tests/config/example_config_files/example.good.toml";
}
enum class BAD_FILES {
UNKNOWN_KEY,
INVALID_TYPE,
INCORRECT_ARRAY_SIZE
};
std::string get_bad_example_file(BAD_FILES type) {
const char* source_root = getenv("MESON_SOURCE_ROOT");
if (source_root == nullptr) {
throw std::runtime_error("MESON_SOURCE_ROOT environment variable is not set.");
}
switch (type) {
case BAD_FILES::UNKNOWN_KEY:
return std::string(source_root) + "/tests/config/example_config_files/example.unknownkey.toml";
case BAD_FILES::INVALID_TYPE:
return std::string(source_root) + "/tests/config/example_config_files/example.invalidtype.toml";
case BAD_FILES::INCORRECT_ARRAY_SIZE:
return std::string(source_root) + "/tests/config/example_config_files/example.incorrectarraysize.toml";
}
throw std::runtime_error("Invalid BAD_FILES type.");
}
// std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/config/example.toml";
/**
* @file configTest.cpp
* @brief Unit tests for the Config class.
*/
class configTestPrivateAccessor {
public:
static bool callIsKeyInCache(fourdst::config::Config& config, const std::string& key) {
return config.isKeyInCache(key);
}
static int callCacheSize(fourdst::config::Config& config) {
return config.configMap.size();
}
static void callAddToCache(fourdst::config::Config& config, const std::string& key, const YAML::Node& node) {
config.addToCache(key, node);
}
static void callRegisterKeyNotFound(fourdst::config::Config& config, const std::string& key) {
config.registerUnknownKey(key);
}
static bool CheckIfKeyUnknown(fourdst::config::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.
@@ -49,64 +57,77 @@ class configTest : public ::testing::Test {};
* @brief Test the constructor of the Config class.
*/
TEST_F(configTest, constructor) {
EXPECT_NO_THROW(fourdst::config::Config::getInstance());
EXPECT_NO_THROW(fourdst::config::Config<TestConfigSchema>());
}
TEST_F(configTest, loadConfig) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
EXPECT_TRUE(config.loadConfig(EXAMPLE_FILENAME));
TEST_F(configTest, load_good_file) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
}
TEST_F(configTest, singletonTest) {
fourdst::config::Config& config1 = fourdst::config::Config::getInstance();
fourdst::config::Config& config2 = fourdst::config::Config::getInstance();
EXPECT_EQ(&config1, &config2);
TEST_F(configTest, load_unknown_key_file) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_THROW(cfg.load(get_bad_example_file(BAD_FILES::UNKNOWN_KEY)), exceptions::ConfigParseError);
}
TEST_F(configTest, getTest) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
config.loadConfig(EXAMPLE_FILENAME);
int maxIter = config.get<int>("opac:lowTemp:numeric:maxIter", 10);
EXPECT_EQ(maxIter, 100);
EXPECT_NE(maxIter, 10);
std::string logLevel = config.get<std::string>("logLevel", "DEBUG");
EXPECT_EQ(logLevel, "INFO");
EXPECT_NE(logLevel, "DEBUG");
float polytropicIndex = config.get<float>("poly:physics:index", 2);
EXPECT_EQ(polytropicIndex, 1.5);
EXPECT_NE(polytropicIndex, 2);
float polytropicIndex2 = config.get<float>("poly:physics:index2", 2.0);
EXPECT_EQ(polytropicIndex2, 2.0);
TEST_F(configTest, load_invalid_type_file) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_THROW(cfg.load(get_bad_example_file(BAD_FILES::INVALID_TYPE)), exceptions::ConfigParseError);
}
TEST_F(configTest, secondSingletonTest) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
EXPECT_EQ(config.get<int>("opac:lowTemp:numeric:maxIter", 10), 100);
TEST_F(configTest, load_incorrect_array_size_file) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_THROW(cfg.load(get_bad_example_file(BAD_FILES::INCORRECT_ARRAY_SIZE)), exceptions::ConfigParseError);
}
TEST_F(configTest, isKeyInCacheTest) {
fourdst::config::Config& config = fourdst::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, check_value) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
EXPECT_EQ(cfg->author, "Example Author");
}
TEST_F(configTest, cacheSize) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
config.loadConfig(EXAMPLE_FILENAME);
EXPECT_EQ(configTestPrivateAccessor::callCacheSize(config), 3);
EXPECT_NE(configTestPrivateAccessor::callCacheSize(config), 4);
config.get<std::string>("outputDir", "DEBUG");
EXPECT_EQ(configTestPrivateAccessor::callCacheSize(config), 4);
TEST_F(configTest, nested_values) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
EXPECT_EQ(cfg->physics.convection, false);
}
TEST_F(configTest, unknownKeyTest) {
fourdst::config::Config& config = fourdst::config::Config::getInstance();
config.loadConfig(EXAMPLE_FILENAME);
config.get<int>("opac:lowTemp:numeric:random", 10);
EXPECT_FALSE(configTestPrivateAccessor::CheckIfKeyUnknown(config, "opac:lowTemp:numeric:maxIter"));
EXPECT_TRUE(configTestPrivateAccessor::CheckIfKeyUnknown(config, "opac:lowTemp:numeric:random"));
TEST_F(configTest, override_default) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
EXPECT_EQ(cfg->simulation.time_step, 0.01);
}
TEST_F(configTest, array_values) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
constexpr std::array<int, 3> expected = {1, 0, 1};
EXPECT_EQ(cfg->physics.flags, expected);
}
TEST_F(configTest, string_values) {
using namespace fourdst::config;
Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
EXPECT_EQ(cfg->output.format, "csv");
}
TEST_F(configTest, save_default) {
using namespace fourdst::config;
const Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.save("TestConfigSchema.toml"));
}
TEST_F(configTest, save_schema) {
using namespace fourdst::config;
const Config<TestConfigSchema> cfg;
EXPECT_NO_THROW(cfg.save_schema("./"));
}

View File

@@ -1,32 +0,0 @@
# High level options
logLevel: "INFO"
outputDir: output
# Module options
poly:
numeric:
newtonTol: 1e-6
newtonMaxIter: 100
gmresTol: 1e-6
gmresMaxIter: 100
physics:
index: 1.5
# Module options
opac:
highTemp:
physics:
table: "/path/to/highTempTable.dat"
numeric:
tol: 1e-6
maxIter: 100
lowTemp:
physics:
table: "/path/to/lowTempTable.dat"
numeric:
tol: 1e-6
maxIter: 100
mesh:
structure:
refine: 2

View File

@@ -0,0 +1,19 @@
[main]
description = "This is an example configuration file."
author = "Example Author"
[main.physics]
diffusion = true
convection = false
radiation = true
flags = [1, 0, 1]
[main.simulation]
time_step = 0.01
total_time = 100.0
output_frequency = 10
[main.output]
format = "csv"
directory = "results/"
save_plots = true

View File

@@ -0,0 +1,19 @@
[main]
description = "This is an example configuration file."
author = "Your Name"
[main.physics]
diffusion = true
convection = false
radiation = true
flags = [1, 2, 3, 5]
[main.simulation]
time_step = 0.01
total_time = 100.0
output_frequency = 10
[main.output]
format = "csv"
directory = "results/"
save_plots = true

View File

@@ -0,0 +1,19 @@
[main]
description = "This is an example configuration file."
author = "Your Name"
[main.physics]
diffusion = "HELLO"
convection = false
radiation = true
flags = [1, 2, 3]
[main.simulation]
time_step = 0.01
total_time = 100.0
output_frequency = 10
[main.output]
format = "csv"
directory = "results/"
save_plots = true

View File

@@ -0,0 +1,19 @@
[main]
description = "This is an example configuration file."
autor = "Your Name"
[main.physics]
diffusion = true
convection = false
radiation = true
flags = [1, 2, 3]
[main.simulation]
time_step = 0.01
total_time = 100.0
output_frequency = 10
[main.output]
format = "csv"
directory = "results/"
save_plots = true

View File

@@ -0,0 +1,30 @@
#pragma once
#include <string>
struct PhysicsConfigOptions {
bool diffusion;
bool convection;
bool radiation;
std::array<int, 3> flags;
};
struct SimulationConfigOptions {
double time_step = 1;
double total_time = 10;
int output_frequency = 1;
};
struct OutputConfigOptions {
std::string directory = "./output";
std::string format = "hdf5";
bool save_plots = false;
};
struct TestConfigSchema {
std::string description;
std::string author;
PhysicsConfigOptions physics;
SimulationConfigOptions simulation;
OutputConfigOptions output;
};