Unverified Commit c525c605 authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #931

Fix several issues with config values
parents 11c4eaca 760b0989
......@@ -415,14 +415,14 @@ private:
const settings& content(const actor_system_config& cfg);
/// Tries to retrieve the value associated to `name` from `cfg`.
/// @relates config_value
/// @relates actor_system_config
template <class T>
optional<T> get_if(const actor_system_config* cfg, string_view name) {
return get_if<T>(&content(*cfg), name);
}
/// Retrieves the value associated to `name` from `cfg`.
/// @relates config_value
/// @relates actor_system_config
template <class T>
T get(const actor_system_config& cfg, string_view name) {
return get<T>(content(cfg), name);
......@@ -430,7 +430,7 @@ T get(const actor_system_config& cfg, string_view name) {
/// Retrieves the value associated to `name` from `cfg` or returns
/// `default_value`.
/// @relates config_value
/// @relates actor_system_config
template <class T, class = typename std::enable_if<
!std::is_pointer<T>::value
&& !std::is_convertible<T, string_view>::value>::type>
......@@ -440,10 +440,17 @@ T get_or(const actor_system_config& cfg, string_view name, T default_value) {
/// Retrieves the value associated to `name` from `cfg` or returns
/// `default_value`.
/// @relates config_value
/// @relates actor_system_config
inline std::string get_or(const actor_system_config& cfg, string_view name,
string_view default_value) {
return get_or(content(cfg), name, default_value);
}
/// Returns whether `xs` associates a value of type `T` to `name`.
/// @relates actor_system_config
template <class T>
bool holds_alternative(const actor_system_config& cfg, string_view name) {
return holds_alternative<T>(content(cfg), name);
}
} // namespace caf
......@@ -259,6 +259,7 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(bool, "boolean");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(double, "real64");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(atom_value, "atom");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(timespan, "timespan");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(uri, "uri");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::list, "list");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary, "dictionary");
......
......@@ -33,12 +33,6 @@ public:
// -- observers --------------------------------------------------------------
/// Checks whether `x` matches the type of this field.
virtual bool type_check(const config_value& x) const noexcept = 0;
/// Returns whether this field in `object` contains valid data.
virtual bool valid(const Object& object) const noexcept = 0;
/// Returns whether this field has a default value.
virtual bool has_default() const noexcept = 0;
......@@ -48,11 +42,14 @@ public:
/// Returns the value of this field in `object` as config value.
virtual config_value get(const Object& object) const = 0;
/// Returns whether calling `set` with `x` would succeed.
virtual bool valid_input(const config_value& x) const = 0;
// -- modifiers --------------------------------------------------------------
/// Sets this field in `object` to `x`.
/// @pre `can_set(x)`
virtual void set(Object& object, const config_value& x) const = 0;
/// Tries to set this field in `object` to `x`.
/// @returns `true` on success, `false` otherwise.
virtual bool set(Object& object, const config_value& x) const = 0;
/// Restores the default value for this field in `object`.
/// @pre `has_default()`
......
......@@ -48,17 +48,19 @@ struct config_value_object_access {
if (!dict)
return false;
for (auto field : Trait::fields()) {
auto value = caf::get_if(dict, field->name());
if (!value) {
if (auto value = caf::get_if(dict, field->name())) {
if (dst) {
if (!field->set(*dst, *value))
return false;
} else {
if (!field->valid_input(*value))
return false;
}
} else {
if (!field->has_default())
return false;
if (dst)
field->set_default(*dst);
} else if (field->type_check(*value)) {
if (dst)
field->set(*dst, *value);
} else {
return false;
}
}
return true;
......@@ -156,10 +158,6 @@ struct config_value_object_access {
ps.code = pec::unexpected_eof;
return;
}
if (!fptr->valid(x)) {
ps.code = pec::illegal_argument;
return;
}
result[fptr->name()] = fptr->get(x);
} while (ps.consume(','));
if (!ps.consume('}')) {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/config_value.hpp"
#include "caf/config_value_field.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/optional.hpp"
namespace caf {
namespace detail {
template <class Object, class Value>
class config_value_field_base : public config_value_field<Object> {
public:
using super = config_value_field<Object>;
using object_type = typename super::object_type;
using value_type = Value;
using predicate_type = bool (*)(const value_type&);
config_value_field_base(string_view name, optional<value_type> default_value,
predicate_type predicate)
: name_(name),
default_value_(std::move(default_value)),
predicate_(predicate) {
// nop
}
config_value_field_base(config_value_field_base&&) = default;
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
}
string_view name() const noexcept override {
return name_;
}
config_value get(const object_type& object) const override {
using access = caf::select_config_value_access_t<value_type>;
return config_value{access::convert(get_value(object))};
}
bool valid_input(const config_value& x) const override {
if (!predicate_)
return holds_alternative<value_type>(x);
if (auto value = get_if<value_type>(&x))
return predicate_(*value);
return false;
}
bool set(object_type& x, const config_value& y) const override {
if (auto value = get_if<value_type>(&y)) {
if (predicate_ && !predicate_(*value))
return false;
set_value(x, move_if_optional(value));
return true;
}
return false;
}
void set_default(object_type& x) const override {
set_value(x, *default_value_);
}
void parse_cli(string_parser_state& ps, object_type& x,
const char* char_blacklist) const override {
value_type tmp;
dispatch_parse_cli(ps, tmp, char_blacklist);
if (ps.code <= pec::trailing_character) {
if (predicate_ && !predicate_(tmp))
ps.code = pec::illegal_argument;
else
set_value(x, std::move(tmp));
}
}
virtual const value_type& get_value(const object_type& object) const = 0;
virtual void set_value(object_type& object, value_type value) const = 0;
protected:
string_view name_;
optional<value_type> default_value_;
predicate_type predicate_;
};
} // namespace detail
} // namespace caf
......@@ -23,6 +23,7 @@
#include "caf/config_value.hpp"
#include "caf/config_value_field.hpp"
#include "caf/detail/config_value_field_base.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp"
......@@ -38,157 +39,110 @@ class config_value_field_impl;
// A config value with direct access to a field via member object pointer.
template <class Value, class Object>
class config_value_field_impl<Value Object::*>
: public config_value_field<Object> {
: public config_value_field_base<Object, Value> {
public:
using super = config_value_field_base<Object, Value>;
using member_pointer = Value Object::*;
using object_type = Object;
using value_type = Value;
using predicate_pointer = bool (*)(const value_type&);
using predicate_type = bool (*)(const value_type&);
constexpr config_value_field_impl(string_view name, member_pointer ptr,
optional<value_type> default_value = none,
predicate_pointer predicate = nullptr)
: name_(name),
ptr_(ptr),
default_value_(std::move(default_value)),
predicate_(predicate) {
predicate_type predicate = nullptr)
: super(name, std::move(default_value), predicate), ptr_(ptr) {
// nop
}
constexpr config_value_field_impl(config_value_field_impl&&) = default;
bool type_check(const config_value& x) const noexcept override {
return holds_alternative<value_type>(x);
}
bool valid(const object_type& x) const noexcept override {
return predicate_ ? predicate_(x.*ptr_) : true;
}
void set(object_type& x, const config_value& y) const override {
x.*ptr_ = caf::get<value_type>(y);
}
config_value get(const object_type& x) const override {
using access = select_config_value_access_t<value_type>;
return config_value{access::convert(x.*ptr_)};
const value_type& get_value(const object_type& x) const override {
return x.*ptr_;
}
void parse_cli(string_parser_state& ps, object_type& x,
const char* char_blacklist) const override {
detail::dispatch_parse_cli(ps, x.*ptr_, char_blacklist);
}
string_view name() const noexcept override {
return name_;
}
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
}
void set_default(object_type& x) const override {
x.*ptr_ = *default_value_;
void set_value(object_type& x, value_type y) const override {
x.*ptr_ = std::move(y);
}
private:
string_view name_;
member_pointer ptr_;
optional<value_type> default_value_;
predicate_pointer predicate_;
};
template <class Get>
struct config_value_field_base {
struct config_value_field_trait {
using trait = get_callable_trait_t<Get>;
static_assert(trait::num_args == 1,
"Get must take exactly one argument (the object)");
using arg_type = tl_head_t<typename trait::arg_types>;
using get_argument_type = tl_head_t<typename trait::arg_types>;
using value_type = decay_t<typename trait::result_type>;
using object_type = decay_t<get_argument_type>;
using type = config_value_field<decay_t<arg_type>>;
};
using get_result_type = typename trait::result_type;
template <class Get>
using config_value_field_base_t = typename config_value_field_base<Get>::type;
using value_type = decay_t<get_result_type>;
};
// A config value with access to a field via getter and setter.
template <class Get, class Set>
class config_value_field_impl<std::pair<Get, Set>>
: public config_value_field_base_t<Get> {
: public config_value_field_base<
typename config_value_field_trait<Get>::object_type,
typename config_value_field_trait<Get>::value_type> {
public:
using super = config_value_field_base_t<Get>;
using trait = config_value_field_trait<Get>;
using object_type = typename trait::object_type;
using object_type = typename super::object_type;
using get_result_type = typename trait::get_result_type;
using get_trait = get_callable_trait_t<Get>;
using value_type = typename trait::value_type;
using value_type = decay_t<typename get_trait::result_type>;
using predicate_type = bool (*)(const value_type&);
using predicate_pointer = bool (*)(const value_type&);
using super = config_value_field_base<object_type, value_type>;
constexpr config_value_field_impl(string_view name, Get getter, Set setter,
optional<value_type> default_value = none,
predicate_pointer predicate = nullptr)
: name_(name),
predicate_type predicate = nullptr)
: super(name, std::move(default_value), predicate),
get_(std::move(getter)),
set_(std::move(setter)),
default_value_(std::move(default_value)),
predicate_(predicate) {
set_(std::move(setter)) {
// nop
}
constexpr config_value_field_impl(config_value_field_impl&&) = default;
bool type_check(const config_value& x) const noexcept override {
return holds_alternative<value_type>(x);
}
void set(object_type& x, const config_value& y) const override {
set_(x, caf::get<value_type>(y));
}
config_value get(const object_type& x) const override {
using access = select_config_value_access_t<value_type>;
return config_value{access::convert(get_(x))};
}
bool valid(const object_type& x) const noexcept override {
return predicate_ ? predicate_(get_(x)) : true;
const value_type& get_value(const object_type& x) const override {
bool_token<std::is_lvalue_reference<get_result_type>::value> token;
return get_value_impl(x, token);
}
void parse_cli(string_parser_state& ps, object_type& x,
const char* char_blacklist) const override {
value_type tmp;
detail::dispatch_parse_cli(ps, tmp, char_blacklist);
if (ps.code <= pec::trailing_character)
set_(x, std::move(tmp));
void set_value(object_type& x, value_type y) const override {
set_(x, std::move(y));
}
string_view name() const noexcept override {
return name_;
}
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
private:
template <class O>
const value_type& get_value_impl(const O& x, std::true_type) const {
return get_(x);
}
void set_default(object_type& x) const override {
set_(x, *default_value_);
template <class O>
const value_type& get_value_impl(const O& x, std::false_type) const {
dummy_ = get_(x);
return dummy_;
}
private:
string_view name_;
Get get_;
Set set_;
optional<value_type> default_value_;
predicate_pointer predicate_;
mutable value_type dummy_;
};
} // namespace detail
......
......@@ -107,30 +107,11 @@ void parse_element(string_parser_state& ps, std::string& x,
template <class T>
enable_if_t<!is_pair<T>::value> parse_element(string_parser_state& ps, T& x,
const char*) {
parse(ps, x);
}
const char*);
template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]) {
static_assert(N > 0, "empty array");
// TODO: consider to guard the blacklist computation with
// `if constexpr (is_same_v<First, string>)` when switching to C++17.
char key_blacklist[N + 1];
if (N > 1)
memcpy(key_blacklist, char_blacklist, N - 1);
key_blacklist[N - 1] = '=';
key_blacklist[N] = '\0';
parse_element(ps, kvp.first, key_blacklist);
if (ps.code > pec::trailing_character)
return;
if (!ps.consume('=')) {
ps.code = pec::unexpected_character;
return;
}
parse_element(ps, kvp.second, char_blacklist);
}
const char (&char_blacklist)[N]);
template <class T>
enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
......@@ -177,6 +158,33 @@ enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
template <class T>
enable_if_t<!is_pair<T>::value> parse_element(string_parser_state& ps, T& x,
const char*) {
parse(ps, x);
}
template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]) {
static_assert(N > 0, "empty array");
// TODO: consider to guard the blacklist computation with
// `if constexpr (is_same_v<First, string>)` when switching to C++17.
char key_blacklist[N + 1];
if (N > 1)
memcpy(key_blacklist, char_blacklist, N - 1);
key_blacklist[N - 1] = '=';
key_blacklist[N] = '\0';
parse_element(ps, kvp.first, key_blacklist);
if (ps.code > pec::trailing_character)
return;
if (!ps.consume('=')) {
ps.code = pec::unexpected_character;
return;
}
parse_element(ps, kvp.second, char_blacklist);
}
// -- convenience functions ----------------------------------------------------
template <class T>
......
......@@ -22,41 +22,64 @@
#include "caf/test/dsl.hpp"
#include <deque>
#include <list>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace caf;
namespace {
// TODO: switch to std::operator""s when switching to C++14
std::string operator"" _s(const char* str, size_t size) {
return std::string{str, size};
}
timespan operator"" _ms(unsigned long long x) {
return std::chrono::duration_cast<timespan>(std::chrono::milliseconds(x));
}
uri operator"" _u(const char* str, size_t size) {
return unbox(make_uri(string_view{str, size}));
}
atom_value operator"" _a(const char* str, size_t size) {
return atom_from_string(string_view{str, size});
}
using string_list = std::vector<std::string>;
struct config : actor_system_config {
config() {
opt_group{custom_options_, "?foo"}
.add<std::string>("bar,b", "some string parameter");
config_option_adder options(string_view category) {
return opt_group{custom_options_, category};
}
void clear() {
content.clear();
remainder.clear();
}
};
struct fixture {
fixture() {
ini << "[foo]\nbar=\"hello\"";
}
config cfg;
error parse(string_list args) {
opts.clear();
remainder.clear();
config cfg;
auto result = cfg.parse(std::move(args), ini);
opts = content(cfg);
remainder = cfg.remainder;
return result;
config_option_adder options(string_view category) {
return cfg.options(category);
}
std::stringstream ini;
settings opts;
std::vector<std::string> remainder;
void parse(const char* file_content, string_list args = {}) {
cfg.clear();
cfg.remainder.clear();
std::istringstream ini{file_content};
if (auto err = cfg.parse(std::move(args), ini))
CAF_FAIL("parse() failed: " << cfg.render(err));
}
};
} // namespace
......@@ -64,37 +87,194 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(actor_system_config_tests, fixture)
CAF_TEST(parsing - without CLI arguments) {
CAF_CHECK_EQUAL(parse({}), none);
CAF_CHECK(remainder.empty());
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "hello");
auto text = "[foo]\nbar=\"hello\"";
options("?foo").add<std::string>("bar,b", "some string parameter");
parse(text);
CAF_CHECK(cfg.remainder.empty());
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "hello");
}
CAF_TEST(parsing - without CLI remainder) {
CAF_TEST(parsing - without CLI cfg.remainder) {
auto text = "[foo]\nbar=\"hello\"";
options("?foo").add<std::string>("bar,b", "some string parameter");
CAF_MESSAGE("CLI long name");
CAF_CHECK_EQUAL(parse({"--foo.bar=test"}), none);
CAF_CHECK(remainder.empty());
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "test");
parse(text, {"--foo.bar=test"});
CAF_CHECK(cfg.remainder.empty());
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "test");
CAF_MESSAGE("CLI abbreviated long name");
CAF_CHECK_EQUAL(parse({"--bar=test"}), none);
CAF_CHECK(remainder.empty());
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "test");
parse(text, {"--bar=test"});
CAF_CHECK(cfg.remainder.empty());
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "test");
CAF_MESSAGE("CLI short name");
CAF_CHECK_EQUAL(parse({"-b", "test"}), none);
CAF_CHECK(remainder.empty());
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "test");
parse(text, {"-b", "test"});
CAF_CHECK(cfg.remainder.empty());
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "test");
CAF_MESSAGE("CLI short name without whitespace");
CAF_CHECK_EQUAL(parse({"-btest"}), none);
CAF_CHECK(remainder.empty());
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "test");
parse(text, {"-btest"});
CAF_CHECK(cfg.remainder.empty());
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "test");
}
CAF_TEST(parsing - with CLI cfg.remainder) {
auto text = "[foo]\nbar=\"hello\"";
options("?foo").add<std::string>("bar,b", "some string parameter");
CAF_MESSAGE("valid cfg.remainder");
parse(text, {"-b", "test", "hello", "world"});
CAF_CHECK_EQUAL(get_or(cfg, "foo.bar", ""), "test");
CAF_CHECK_EQUAL(cfg.remainder, string_list({"hello", "world"}));
CAF_MESSAGE("invalid cfg.remainder");
}
// Checks whether both a synced variable and the corresponding entry in
// content(cfg) are equal to `value`.
#define CHECK_SYNCED(var, value) \
do { \
CAF_CHECK_EQUAL(var, value); \
CAF_CHECK_EQUAL(get_if<decltype(var)>(&cfg, #var), value); \
} while (false)
// Checks whether an entry in content(cfg) is equal to `value`.
#define CHECK_TEXT_ONLY(type, var, value) \
CAF_CHECK_EQUAL(get_if<type>(&cfg, #var), value)
#define ADD(var) add(var, #var, "...")
#define VAR(type) \
auto some_##type = type{}; \
options("global").add(some_##type, "some_" #type, "...")
#define NAMED_VAR(type, name) \
auto name = type{}; \
options("global").add(name, #name, "...")
CAF_TEST(integers and integer containers options) {
// Use a wild mess of "list-like" and "map-like" containers from the STL.
using int_list = std::vector<int>;
using int_list_list = std::list<std::deque<int>>;
using int_map = std::unordered_map<std::string, int>;
using int_list_map = std::map<std::string, std::unordered_set<int>>;
using int_map_list = std::set<std::map<std::string, int>>;
auto text = R"__(
some_int = 42
yet_another_int = 123
some_int_list = [1, 2, 3]
some_int_list_list = [[1, 2, 3], [4, 5, 6]]
some_int_map = {a = 1, b = 2, c = 3}
some_int_list_map = {a = [1, 2, 3], b = [4, 5, 6]}
some_int_map_list = [{a = 1, b = 2, c = 3}, {d = 4, e = 5, f = 6}]
)__";
NAMED_VAR(int, some_other_int);
VAR(int);
VAR(int_list);
VAR(int_list_list);
VAR(int_map);
VAR(int_list_map);
VAR(int_map_list);
parse(text, {"--some_other_int=23"});
CHECK_SYNCED(some_int, 42);
CHECK_SYNCED(some_other_int, 23);
CHECK_TEXT_ONLY(int, yet_another_int, 123);
CHECK_SYNCED(some_int_list, int_list({1, 2, 3}));
CHECK_SYNCED(some_int_list_list, int_list_list({{1, 2, 3}, {4, 5, 6}}));
CHECK_SYNCED(some_int_map, int_map({{{"a", 1}, {"b", 2}, {"c", 3}}}));
CHECK_SYNCED(some_int_list_map,
int_list_map({{{"a", {1, 2, 3}}, {"b", {4, 5, 6}}}}));
CHECK_SYNCED(some_int_map_list,
int_map_list({{{"a", 1}, {"b", 2}, {"c", 3}},
{{"d", 4}, {"e", 5}, {"f", 6}}}));
}
CAF_TEST(parsing - with CLI remainder) {
CAF_MESSAGE("valid remainder");
CAF_CHECK_EQUAL(parse({"-b", "test", "hello", "world"}), none);
CAF_CHECK_EQUAL(get_or(opts, "foo.bar", ""), "test");
CAF_CHECK_EQUAL(remainder, string_list({"hello", "world"}));
CAF_MESSAGE("invalid remainder");
CAF_CHECK_NOT_EQUAL(parse({"-b", "test", "-abc"}), none);
CAF_TEST(basic and basic containers options) {
using std::map;
using std::string;
using std::vector;
using int_list = vector<int>;
using bool_list = vector<bool>;
using double_list = vector<double>;
using atom_value_list = vector<atom_value>;
using timespan_list = vector<timespan>;
using uri_list = vector<uri>;
using string_list = vector<string>;
using int_map = map<string, int>;
using bool_map = map<string, bool>;
using double_map = map<string, double>;
using atom_value_map = map<string, atom_value>;
using timespan_map = map<string, timespan>;
using uri_map = map<string, uri>;
using string_map = map<string, string>;
auto text = R"__(
some_int = 42
some_bool = true
some_double = 1e23
some_atom_value = 'atom'
some_timespan = 123ms
some_uri = <foo:bar>
some_string = "string"
some_int_list = [1, 2, 3]
some_bool_list = [false, true]
some_double_list = [1., 2., 3.]
some_atom_value_list = ['a', 'b', 'c']
some_timespan_list = [123ms, 234ms, 345ms]
some_uri_list = [<foo:a>, <foo:b>, <foo:c>]
some_string_list = ["a", "b", "c"]
some_int_map = {a = 1, b = 2, c = 3}
some_bool_map = {a = true, b = false}
some_double_map = {a = 1., b = 2., c = 3.}
some_atom_value_map = {a = '1', b = '2', c = '3'}
some_timespan_map = {a = 123ms, b = 234ms, c = 345ms}
some_uri_map = {a = <foo:a>, b = <foo:b>, c = <foo:c>}
some_string_map = {a = "1", b = "2", c = "3"}
)__";
VAR(int);
VAR(bool);
VAR(double);
VAR(atom_value);
VAR(timespan);
VAR(uri);
VAR(string);
VAR(int_list);
VAR(bool_list);
VAR(double_list);
VAR(atom_value_list);
VAR(timespan_list);
VAR(uri_list);
VAR(string_list);
VAR(int_map);
VAR(bool_map);
VAR(double_map);
VAR(atom_value_map);
VAR(timespan_map);
VAR(uri_map);
VAR(string_map);
parse(text);
CAF_MESSAGE("check primitive types");
CHECK_SYNCED(some_int, 42);
CHECK_SYNCED(some_bool, true);
CHECK_SYNCED(some_double, 1e23);
CHECK_SYNCED(some_atom_value, "atom"_a);
CHECK_SYNCED(some_timespan, 123_ms);
CHECK_SYNCED(some_uri, "foo:bar"_u);
CHECK_SYNCED(some_string, "string"_s);
CAF_MESSAGE("check list types");
CHECK_SYNCED(some_int_list, int_list({1, 2, 3}));
CHECK_SYNCED(some_bool_list, bool_list({false, true}));
CHECK_SYNCED(some_double_list, double_list({1., 2., 3.}));
CHECK_SYNCED(some_atom_value_list, atom_value_list({"a"_a, "b"_a, "c"_a}));
CHECK_SYNCED(some_timespan_list, timespan_list({123_ms, 234_ms, 345_ms}));
CHECK_SYNCED(some_uri_list, uri_list({"foo:a"_u, "foo:b"_u, "foo:c"_u}));
CHECK_SYNCED(some_string_list, string_list({"a", "b", "c"}));
CAF_MESSAGE("check dictionary types");
CHECK_SYNCED(some_int_map, int_map({{"a", 1}, {"b", 2}, {"c", 3}}));
CHECK_SYNCED(some_bool_map, bool_map({{"a", true}, {"b", false}}));
CHECK_SYNCED(some_double_map, double_map({{"a", 1.}, {"b", 2.}, {"c", 3.}}));
CHECK_SYNCED(some_atom_value_map,
atom_value_map({{"a", "1"_a}, {"b", "2"_a}, {"c", "3"_a}}));
CHECK_SYNCED(some_timespan_map,
timespan_map({{"a", 123_ms}, {"b", 234_ms}, {"c", 345_ms}}));
CHECK_SYNCED(some_uri_map,
uri_map({{"a", "foo:a"_u}, {"b", "foo:b"_u}, {"c", "foo:c"_u}}));
CHECK_SYNCED(some_string_map,
string_map({{"a", "1"}, {"b", "2"}, {"c", "3"}}));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -22,6 +22,7 @@
#include "caf/test/dsl.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/config_option_set.hpp"
#include "caf/config_value_object_access.hpp"
......@@ -130,15 +131,12 @@ struct fixture {
foobar x;
CAF_CHECK_EQUAL(foo_field.name(), "foo");
CAF_REQUIRE(foo_field.has_default());
CAF_CHECK(foo_field.valid(x));
CAF_CHECK_EQUAL(foo_field.get(x), config_value(0));
foo_field.set_default(x);
CAF_CHECK_EQUAL(foo_field.get(x), config_value(42));
CAF_CHECK(!foo_field.type_check(config_value(1.)));
CAF_CHECK(foo_field.type_check(config_value(-1)));
foo_field.set(x, config_value(-1));
CAF_CHECK_EQUAL(foo_field.get(x), config_value(-1));
CAF_CHECK(!foo_field.valid(x));
CAF_CHECK(!foo_field.valid_input(config_value(1.)));
CAF_CHECK(!foo_field.valid_input(config_value(-1)));
CAF_CHECK(!foo_field.set(x, config_value(-1)));
string_view input = "123";
string_parser_state ps{input.begin(), input.end()};
foo_field.parse_cli(ps, x);
......@@ -257,4 +255,65 @@ CAF_TEST(object access from CLI arguments - foobar_foobar) {
fbfb({123, "hello"}, {1, "world!"}));
}
namespace {
constexpr const char* config_text = R"__(
arg1 = {
foo = 42
bar = "Don't panic!"
}
arg2 = {
x = {
foo = 1
bar = "hello"
}
y = {
foo = 2
bar = "world"
}
}
)__";
struct test_config : actor_system_config {
test_config() {
opt_group{custom_options_, "global"}
.add(fb, "arg1,1", "some foobar")
.add(fbfb, "arg2,2", "somme foobar-foobar");
}
foobar fb;
foobar_foobar fbfb;
};
} // namespace
CAF_TEST(object access from actor system config - file input) {
test_config cfg;
std::istringstream in{config_text};
if (auto err = cfg.parse(0, nullptr, in))
CAF_FAIL("cfg.parse failed: " << cfg.render(err));
CAF_CHECK_EQUAL(cfg.fb.foo, 42);
CAF_CHECK_EQUAL(cfg.fb.bar, "Don't panic!");
CAF_CHECK_EQUAL(cfg.fbfb.x.foo, 1);
CAF_CHECK_EQUAL(cfg.fbfb.y.foo, 2);
CAF_CHECK_EQUAL(cfg.fbfb.x.bar, "hello");
CAF_CHECK_EQUAL(cfg.fbfb.y.bar, "world");
}
CAF_TEST(object access from actor system config - file input and arguments) {
std::vector<std::string> args{
"-2",
"{y = {bar = CAF, foo = 20}, x = {foo = 10, bar = hello}}",
};
test_config cfg;
std::istringstream in{config_text};
if (auto err = cfg.parse(std::move(args), in))
CAF_FAIL("cfg.parse failed: " << cfg.render(err));
CAF_CHECK_EQUAL(cfg.fb.foo, 42);
CAF_CHECK_EQUAL(cfg.fb.bar, "Don't panic!");
CAF_CHECK_EQUAL(cfg.fbfb.x.foo, 10);
CAF_CHECK_EQUAL(cfg.fbfb.y.foo, 20);
CAF_CHECK_EQUAL(cfg.fbfb.x.bar, "hello");
CAF_CHECK_EQUAL(cfg.fbfb.y.bar, "CAF");
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment