Commit 9dbfba7c authored by Dominik Charousset's avatar Dominik Charousset

Remove custom CLI parsing

- Consistently use the same parser on all `config_value` inputs.
- Ensure that CAF accepts JSON notation in `get_as` and `get_or`.
- Remove error-prone auto-unboxing for variants in the testing DSL.
parent 31be7eb2
......@@ -325,6 +325,13 @@ private:
/// Returns all user-provided configuration parameters.
CAF_CORE_EXPORT const settings& content(const actor_system_config& cfg);
/// 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);
}
/// Tries to retrieve the value associated to `name` from `cfg`.
/// @relates actor_system_config
template <class T>
......@@ -339,29 +346,20 @@ T get(const actor_system_config& cfg, string_view name) {
return get<T>(content(cfg), name);
}
/// Retrieves the value associated to `name` from `cfg` or returns
/// `default_value`.
/// Retrieves the value associated to `name` from `cfg` or returns `fallback`.
/// @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>
T get_or(const actor_system_config& cfg, string_view name, T default_value) {
return get_or(content(cfg), name, std::move(default_value));
template <class To = get_or_auto_deduce, class Fallback>
auto get_or(const actor_system_config& cfg, string_view name,
Fallback&& fallback) {
return get_or<To>(content(cfg), name, std::forward<Fallback>(fallback));
}
/// Retrieves the value associated to `name` from `cfg` or returns
/// `default_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`.
/// Tries to retrieve the value associated to `name` from `cfg` as an instance
/// of type `T`.
/// @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);
expected<T> get_as(const actor_system_config& cfg, string_view name) {
return get_as<T>(content(cfg), name);
}
} // namespace caf
......@@ -48,7 +48,7 @@ public:
config_value (*get)(const void*);
/// Human-readable name of the option's type.
std::string type_name;
string_view type_name;
};
// -- constructors, destructors, and assignment operators --------------------
......
......@@ -54,6 +54,32 @@
#include "caf/uri.hpp"
#include "caf/variant.hpp"
namespace caf::detail {
template <class T>
struct is_config_value_type : std::false_type {};
#define CAF_ADD_CONFIG_VALUE_TYPE(type_name) \
template <> \
struct is_config_value_type<type_name> : std::true_type {}
CAF_ADD_CONFIG_VALUE_TYPE(none_t);
CAF_ADD_CONFIG_VALUE_TYPE(int64_t);
CAF_ADD_CONFIG_VALUE_TYPE(bool);
CAF_ADD_CONFIG_VALUE_TYPE(double);
CAF_ADD_CONFIG_VALUE_TYPE(timespan);
CAF_ADD_CONFIG_VALUE_TYPE(uri);
CAF_ADD_CONFIG_VALUE_TYPE(std::string);
CAF_ADD_CONFIG_VALUE_TYPE(std::vector<config_value>);
CAF_ADD_CONFIG_VALUE_TYPE(dictionary<config_value>);
#undef CAF_ADD_CONFIG_VALUE_TYPE
template <class T>
constexpr bool is_config_value_type_v = is_config_value_type<T>::value;
}; // namespace caf::detail
namespace caf {
/// A type for config parameters with similar interface to a `variant`. This
......@@ -61,11 +87,6 @@ namespace caf {
/// contain lists of themselves.
class CAF_CORE_EXPORT config_value {
public:
// -- friends ----------------------------------------------------------------
template <class T>
friend expected<T> get_as(const config_value& value);
// -- member types -----------------------------------------------------------
using integer = int64_t;
......@@ -181,6 +202,9 @@ public:
return data_.index() == 0;
}
/// @private
ptrdiff_t signed_index() const noexcept;
// -- utility ----------------------------------------------------------------
/// @private
......@@ -201,6 +225,9 @@ public:
/// @private
expected<timespan> to_timespan() const;
/// @private
expected<uri> to_uri() const;
/// @private
expected<list> to_list() const;
......@@ -221,14 +248,29 @@ public:
return {reader.move_error()};
}
/// @experimental
template <class T>
error assign(const T& x) {
config_value_writer writer{this};
if (writer.apply(x))
if constexpr (detail::is_config_value_type_v<T>) {
data_ = x;
return {};
else
return {writer.move_error()};
} else {
config_value_writer writer{this};
if (writer.apply(x))
return {};
else
return {writer.move_error()};
}
}
template <class T>
static constexpr string_view mapped_type_name() {
if constexpr (detail::is_complete<caf::type_name<T>>) {
return caf::type_name<T>::value;
} else if constexpr (detail::is_list_like_v<T>) {
return "list";
} else {
return "dictionary";
}
}
private:
......@@ -241,39 +283,12 @@ private:
// -- auto conversion of related types ---------------------------------------
void set(none_t) {
data_ = none;
}
void set(bool x) {
data_ = x;
}
void set(float x) {
data_ = static_cast<double>(x);
}
void set(const char* x) {
data_ = std::string{x};
}
void set(string_view x) {
data_ = std::string{x.begin(), x.end()};
}
template <class T>
detail::enable_if_t<
detail::is_one_of<T, real, timespan, uri, string, list, dictionary>::value>
set(T x) {
data_ = std::move(x);
}
template <class T>
void set_range(T& xs, std::true_type) {
auto& dict = as_dictionary();
dict.clear();
for (auto& kvp : xs)
dict.emplace(kvp.first, std::move(kvp.second));
for (auto& [key, val] : xs)
dict.emplace(key, std::move(val));
}
template <class T>
......@@ -285,17 +300,29 @@ private:
}
template <class T>
detail::enable_if_t<detail::is_iterable<T>::value
&& !detail::is_one_of<T, string, list, dictionary>::value>
set(T xs) {
using value_type = typename T::value_type;
detail::bool_token<detail::is_pair<value_type>::value> is_map_type;
set_range(xs, is_map_type);
void set(T x) {
if constexpr (detail::is_config_value_type_v<T>) {
data_ = std::move(x);
} else if constexpr (std::is_integral<T>::value) {
data_ = static_cast<int64_t>(x);
} else {
static_assert(detail::is_iterable<T>::value);
using value_type = typename T::value_type;
detail::bool_token<detail::is_pair<value_type>::value> is_map_type;
set_range(x, is_map_type);
}
}
template <class T>
detail::enable_if_t<std::is_integral<T>::value> set(T x) {
data_ = static_cast<int64_t>(x);
void set(float x) {
data_ = static_cast<double>(x);
}
void set(const char* x) {
data_ = std::string{x};
}
void set(string_view x) {
data_ = std::string{x.begin(), x.end()};
}
// -- member variables -------------------------------------------------------
......@@ -308,9 +335,6 @@ CAF_CORE_EXPORT std::string to_string(const config_value& x);
// -- conversion via get_as ----------------------------------------------------
template <class T>
expected<T> get_as(const config_value& value);
template <class T>
expected<T> get_as(const config_value&, inspector_access_type::none) {
static_assert(detail::always_false_v<T>,
......@@ -476,6 +500,8 @@ expected<T> get_as(const config_value& value) {
return value.to_list();
} else if constexpr (std::is_same<T, config_value::dictionary>::value) {
return value.to_dictionary();
} else if constexpr (std::is_same<T, uri>::value) {
return value.to_uri();
} else {
auto token = inspect_access_type<config_value_reader, T>();
return get_as<T>(value, token);
......@@ -505,6 +531,14 @@ struct get_or_deduction_guide<string_view> {
}
};
template <>
struct get_or_deduction_guide<const char*> {
using value_type = std::string;
static value_type convert(const char* str) {
return {str};
}
};
template <class T>
struct get_or_deduction_guide<span<T>> {
using value_type = std::vector<T>;
......@@ -539,575 +573,93 @@ auto get_or(const config_value& x, Fallback&& fallback) {
// -- SumType-like access ------------------------------------------------------
template <class T>
struct default_config_value_access {
static bool is(const config_value& x) {
return holds_alternative<T>(x.get_data());
}
static const T* get_if(const config_value* x) {
return caf::get_if<T>(&(x->get_data()));
}
static T get(const config_value& x) {
return caf::get<T>(x.get_data());
}
static T convert(T x) {
return x;
}
};
/// @relates config_value
template <class T>
struct config_value_access;
#define CAF_DEFAULT_CONFIG_VALUE_ACCESS(type, name) \
template <> \
struct CAF_CORE_EXPORT config_value_access<type> \
: default_config_value_access<type> { \
static std::string type_name() { \
return name; \
} \
}
CAF_DEFAULT_CONFIG_VALUE_ACCESS(bool, "boolean");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(double, "real64");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(uri, "uri");
#undef CAF_DEFAULT_CONFIG_VALUE_ACCESS
template <>
struct CAF_CORE_EXPORT config_value_access<timespan> {
static std::string type_name() {
return "timespan";
}
static bool is(const config_value& x) {
return static_cast<bool>(get_if(&x));
}
static optional<timespan> get_if(const config_value* x) {
auto data_ptr = std::addressof(x->get_data());
if (auto res = caf::get_if<timespan>(data_ptr))
return static_cast<timespan>(*res);
if (auto str = caf::get_if<std::string>(data_ptr)) {
string_view sv{*str};
timespan result;
string_parser_state ps{sv.begin(), sv.end()};
detail::parse(ps, result);
if (ps.code == pec::success)
return result;
}
return none;
}
static timespan get(const config_value& x) {
auto result = get_if(&x);
CAF_ASSERT(result);
return *result;
}
static timespan convert(timespan x) {
return x;
}
};
template <>
struct CAF_CORE_EXPORT config_value_access<float> {
static std::string type_name() {
return "real32";
}
static bool is(const config_value& x) {
return holds_alternative<double>(x.get_data());
}
static optional<float> get_if(const config_value* x) {
if (auto res = caf::get_if<double>(&(x->get_data())))
return static_cast<float>(*res);
return none;
}
static float get(const config_value& x) {
return static_cast<float>(caf::get<double>(x.get_data()));
}
static double convert(float x) {
return x;
}
};
template <>
struct CAF_CORE_EXPORT config_value_access<std::string> {
using super = default_config_value_access<std::string>;
static std::string type_name() {
return "string";
}
static bool is(const config_value& x) {
return holds_alternative<std::string>(x.get_data());
}
static const std::string* get_if(const config_value* x) {
return caf::get_if<std::string>(&(x->get_data()));
}
static std::string get(const config_value& x) {
return caf::get<std::string>(x.get_data());
}
static std::string convert(std::string x) {
return x;
}
};
// -- implementation details for get/get_if/holds_alternative ------------------
namespace detail {
/// Wraps tag types for static dispatching.
/// @relates config_value_access_type
struct config_value_access_type {
/// Flags types that provide a `config_value_access` specialization.
struct specialization {};
/// Flags builtin integral types.
struct integral {};
/// Flags types with `std::tuple`-like API.
struct tuple {};
/// Flags types with `std::map`-like API.
struct map {};
/// Flags types with `std::vector`-like API.
struct list {};
/// Flags types without default access that shall fall back to using the
/// inspection API.
struct inspect {};
};
/// @relates config_value_access_type
template <class T>
constexpr auto config_value_access_token() {
if constexpr (detail::is_complete<config_value_access<T>>)
return config_value_access_type::specialization{};
else if constexpr (std::is_integral<T>::value)
return config_value_access_type::integral{};
else if constexpr (detail::is_stl_tuple_type_v<T>)
return config_value_access_type::tuple{};
else if constexpr (detail::is_map_like<T>::value)
return config_value_access_type::map{};
else if constexpr (detail::is_list_like<T>::value)
return config_value_access_type::list{};
[[deprecated("use get_as or get_or instead")]] optional<T>
legacy_get_if(const config_value* x) {
if (auto val = get_as<T>(*x))
return {std::move(*val)};
else
return config_value_access_type::inspect{};
return {};
}
template <class T>
struct integral_config_value_access;
template <class T>
struct tuple_config_value_access;
template <class T>
struct map_config_value_access;
template <class T>
struct list_config_value_access;
template <class T>
struct inspect_config_value_access;
template <class T, class Token>
struct config_value_access_oracle;
auto get_if(const config_value* x) {
if constexpr (detail::is_config_value_type_v<T>) {
return get_if<T>(x->get_data_ptr());
} else {
return legacy_get_if<T>(x);
}
}
template <class T>
struct config_value_access_oracle<T, config_value_access_type::specialization> {
using type = config_value_access<T>;
};
#define CAF_CONFIG_VALUE_ACCESS_ORACLE(access_type) \
template <class T> \
struct config_value_access_oracle<T, \
config_value_access_type::access_type> { \
using type = access_type##_config_value_access<T>; \
auto get_if(config_value* x) {
if constexpr (detail::is_config_value_type_v<T>) {
return get_if<T>(x->get_data_ptr());
} else {
return legacy_get_if<T>(x);
}
CAF_CONFIG_VALUE_ACCESS_ORACLE(integral);
CAF_CONFIG_VALUE_ACCESS_ORACLE(tuple);
CAF_CONFIG_VALUE_ACCESS_ORACLE(map);
CAF_CONFIG_VALUE_ACCESS_ORACLE(list);
CAF_CONFIG_VALUE_ACCESS_ORACLE(inspect);
#undef CAF_CONFIG_VALUE_ACCESS_ORACLE
}
template <class T>
using config_value_access_t = typename config_value_access_oracle<
T, decltype(config_value_access_token<T>())>::type;
[[deprecated("use get_as or get_or instead")]] T
legacy_get(const config_value& x) {
auto val = get_as<T>(x);
if (!val)
CAF_RAISE_ERROR("legacy_get: conversion failed");
return std::move(*val);
}
template <class T>
struct integral_config_value_access {
using integer_type = config_value::integer;
static std::string type_name() {
std::string result;
if (std::is_signed<T>::value)
result = "int";
else
result = "uint";
result += std::to_string(sizeof(T) * 8);
return result;
}
static bool is(const config_value& x) {
auto ptr = caf::get_if<integer_type>(x.get_data_ptr());
return ptr != nullptr && detail::bounds_checker<T>::check(*ptr);
}
static optional<T> get_if(const config_value* x) {
auto ptr = caf::get_if<integer_type>(x->get_data_ptr());
if (ptr != nullptr && detail::bounds_checker<T>::check(*ptr))
return static_cast<T>(*ptr);
return none;
}
static T get(const config_value& x) {
auto res = get_if(&x);
CAF_ASSERT(res != none);
return *res;
}
static T convert(T x) {
return x;
decltype(auto) get(const config_value& x) {
if constexpr (detail::is_config_value_type_v<T>) {
return get<T>(x.get_data());
} else {
return legacy_get<T>(x);
}
};
}
template <class T>
struct list_config_value_access {
using list_type = T;
using value_type = typename list_type::value_type;
using value_access = config_value_access_t<value_type>;
static std::string type_name() {
return "list of " + value_access::type_name();
}
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::list>(&(x.get_data()));
return lst != nullptr
&& std::all_of(lst->begin(), lst->end(), [](const config_value& y) {
return caf::holds_alternative<value_type>(y);
});
return false;
}
static optional<list_type> get_if(const config_value* x) {
list_type result;
auto out = std::inserter(result, result.end());
auto extract = [&](const config_value& y) {
if (auto opt = caf::get_if<value_type>(&y)) {
*out++ = move_if_optional(opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::list>(&(x->get_data()));
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static list_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
static config_value::list convert(const list_type& xs) {
config_value::list result;
for (const auto& x : xs)
result.emplace_back(value_access::convert(x));
return result;
decltype(auto) get(config_value& x) {
if constexpr (detail::is_config_value_type_v<T>) {
return get<T>(x.get_data());
} else {
return legacy_get<T>(x);
}
};
}
template <class T>
struct map_config_value_access {
using map_type = T;
using mapped_type = typename map_type::mapped_type;
using mapped_access = config_value_access_t<mapped_type>;
static std::string type_name() {
std::string result = "dictionary of ";
auto nested_name = mapped_access::type_name();
result.insert(result.end(), nested_name.begin(), nested_name.end());
return result;
}
static bool is(const config_value& x) {
using value_type = config_value::dictionary::value_type;
auto is_mapped_type = [](const value_type& y) {
return caf::holds_alternative<mapped_type>(y.second);
};
if (auto dict = caf::get_if<config_value::dictionary>(&(x.get_data())))
return std::all_of(dict->begin(), dict->end(), is_mapped_type);
return false;
}
static optional<map_type> get_if(const config_value* x) {
using value_type = config_value::dictionary::value_type;
map_type result;
auto extract = [&](const value_type& y) {
if (auto opt = caf::get_if<mapped_type>(&y.second)) {
result.emplace(y.first, move_if_optional(opt));
return true;
}
return false;
};
if (auto dict = caf::get_if<config_value::dictionary>(&(x->get_data())))
if (std::all_of(dict->begin(), dict->end(), extract))
return result;
return none;
}
static map_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
static config_value::dictionary convert(const map_type& xs) {
config_value::dictionary result;
for (const auto& x : xs)
result.emplace(x.first, mapped_access::convert(x.second));
return result;
}
};
template <class... Ts>
struct tuple_config_value_access<std::tuple<Ts...>> {
using tuple_type = std::tuple<Ts...>;
static std::string type_name() {
auto result = "tuple[";
rec_name(result, true, detail::int_token<0>(), detail::type_list<Ts...>());
result += ']';
return result;
}
static bool is(const config_value& x) {
if (auto lst = caf::get_if<config_value::list>(&(x.get_data()))) {
if (lst->size() != sizeof...(Ts))
return false;
return rec_is(*lst, detail::int_token<0>(), detail::type_list<Ts...>());
}
return false;
}
static optional<tuple_type> get_if(const config_value* x) {
if (auto lst = caf::get_if<config_value::list>(&(x->get_data()))) {
if (lst->size() != sizeof...(Ts))
return none;
tuple_type result;
if (rec_get(*lst, result, detail::int_token<0>(),
detail::type_list<Ts...>()))
return result;
}
return none;
}
static tuple_type get(const config_value& x) {
if (auto result = get_if(&x))
return std::move(*result);
CAF_RAISE_ERROR("invalid type found");
}
static config_value::list convert(const tuple_type& xs) {
config_value::list result;
rec_convert(result, xs, detail::int_token<0>(), detail::type_list<Ts...>());
return result;
}
private:
template <int Pos>
static void
rec_name(std::string&, bool, detail::int_token<Pos>, detail::type_list<>) {
// nop
}
template <int Pos, class U, class... Us>
static void rec_name(std::string& result, bool is_first,
detail::int_token<Pos>, detail::type_list<U, Us...>) {
if (!is_first)
result += ", ";
using nested = config_value_access<U>;
auto nested_name = nested::type_name();
result.insert(result.end(), nested_name.begin(), nested_name.end());
return rec_name(result, false, detail::int_token<Pos + 1>(),
detail::type_list<Us...>());
}
template <int Pos>
static bool rec_is(const config_value::list&, detail::int_token<Pos>,
detail::type_list<>) {
[[deprecated("use get_as or get_or instead")]] bool
legacy_holds_alternative(const config_value& x) {
if (auto val = get_as<T>(x))
return true;
}
template <int Pos, class U, class... Us>
static bool rec_is(const config_value::list& xs, detail::int_token<Pos>,
detail::type_list<U, Us...>) {
if (!holds_alternative<U>(xs[Pos]))
return false;
return rec_is(xs, detail::int_token<Pos + 1>(), detail::type_list<Us...>());
}
template <int Pos>
static bool rec_get(const config_value::list&, tuple_type&,
detail::int_token<Pos>, detail::type_list<>) {
return true;
}
template <int Pos, class U, class... Us>
static bool rec_get(const config_value::list& xs, tuple_type& result,
detail::int_token<Pos>, detail::type_list<U, Us...>) {
if (auto value = caf::get_if<U>(&xs[Pos])) {
std::get<Pos>(result) = detail::move_if_not_ptr(value);
return rec_get(xs, result, detail::int_token<Pos + 1>(),
detail::type_list<Us...>());
}
else
return false;
}
template <int Pos>
static void rec_convert(config_value::list&, const tuple_type&,
detail::int_token<Pos>, detail::type_list<>) {
// nop
}
template <int Pos, class U, class... Us>
static void rec_convert(config_value::list& result, const tuple_type& xs,
detail::int_token<Pos>, detail::type_list<U, Us...>) {
using trait = config_value_access_t<U>;
result.emplace_back(trait::convert(std::get<Pos>(xs)));
return rec_convert(result, xs, detail::int_token<Pos + 1>(),
detail::type_list<Us...>());
}
};
} // namespace detail
// -- SumType access of dictionary values --------------------------------------
template <>
struct sum_type_access<config_value> {
using types = typename config_value::types;
using type0 = typename detail::tl_head<types>::type;
static constexpr bool specialized = true;
template <class U, int Pos>
static bool is(const config_value& x, sum_type_token<U, Pos> token) {
return x.get_data().is(pos(token));
}
template <class U>
static bool is(const config_value& x, sum_type_token<U, -1>) {
return detail::config_value_access_t<U>::is(x);
}
template <class U, int Pos>
static U& get(config_value& x, sum_type_token<U, Pos> token) {
return x.get_data().get(pos(token));
}
template <class U>
static U get(config_value& x, sum_type_token<U, -1>) {
return detail::config_value_access_t<U>::get(x);
}
template <class U, int Pos>
static const U& get(const config_value& x, sum_type_token<U, Pos> token) {
return x.get_data().get(pos(token));
}
template <class U>
static U get(const config_value& x, sum_type_token<U, -1>) {
return detail::config_value_access_t<U>::get(x);
}
template <class U, int Pos>
static U* get_if(config_value* x, sum_type_token<U, Pos> token) {
return is(*x, token) ? &get(*x, token) : nullptr;
}
template <class U>
static optional<U> get_if(config_value* x, sum_type_token<U, -1>) {
return detail::config_value_access_t<U>::get_if(x);
}
template <class U, int Pos>
static const U* get_if(const config_value* x, sum_type_token<U, Pos> token) {
return is(*x, token) ? &get(*x, token) : nullptr;
}
template <class U>
static optional<U> get_if(const config_value* x, sum_type_token<U, -1>) {
return detail::config_value_access_t<U>::get_if(x);
}
template <class Result, class Visitor, class... Ts>
static Result apply(config_value& x, Visitor&& visitor, Ts&&... xs) {
return x.get_data().template apply<Result>(std::forward<Visitor>(visitor),
std::forward<Ts>(xs)...);
}
}
template <class Result, class Visitor, class... Ts>
static Result apply(const config_value& x, Visitor&& visitor, Ts&&... xs) {
return x.get_data().template apply<Result>(std::forward<Visitor>(visitor),
std::forward<Ts>(xs)...);
template <class T>
auto holds_alternative(const config_value& x) {
if constexpr (detail::is_config_value_type_v<T>) {
return holds_alternative<T>(x.get_data());
} else {
return legacy_holds_alternative<T>(x);
}
};
/// @relates config_value
inline bool operator==(const config_value& x, std::nullptr_t) noexcept {
return x.get_data().index() == 0;
}
/// @relates config_value
inline bool operator==(std::nullptr_t, const config_value& x) noexcept {
return x.get_data().index() == 0;
}
// -- comparison operator overloads --------------------------------------------
/// @relates config_value
inline bool operator!=(const config_value& x, std::nullptr_t) noexcept {
return x.get_data().index() != 0;
}
CAF_CORE_EXPORT bool operator<(const config_value& x, const config_value& y);
/// @relates config_value
inline bool operator!=(std::nullptr_t, const config_value& x) noexcept {
return x.get_data().index() != 0;
}
CAF_CORE_EXPORT bool operator<=(const config_value& x, const config_value& y);
/// @relates config_value
CAF_CORE_EXPORT bool operator<(const config_value& x, const config_value& y);
CAF_CORE_EXPORT bool operator==(const config_value& x, const config_value& y);
/// @relates config_value
CAF_CORE_EXPORT bool operator==(const config_value& x, const config_value& y);
CAF_CORE_EXPORT bool operator>(const config_value& x, const config_value& y);
/// @relates config_value
inline bool operator>=(const config_value& x, const config_value& y) {
return !(x < y);
}
CAF_CORE_EXPORT bool operator>=(const config_value& x, const config_value& y);
/// @relates config_value
inline bool operator!=(const config_value& x, const config_value& y) {
......@@ -1118,6 +670,8 @@ inline bool operator!=(const config_value& x, const config_value& y) {
CAF_CORE_EXPORT std::ostream& operator<<(std::ostream& out,
const config_value& x);
// -- convenience APIs ---------------------------------------------------------
template <class... Ts>
config_value make_config_value_list(Ts&&... xs) {
std::vector<config_value> lst{config_value{std::forward<Ts>(xs)}...};
......@@ -1126,44 +680,6 @@ config_value make_config_value_list(Ts&&... xs) {
// -- inspection API -----------------------------------------------------------
namespace detail {
template <class T>
struct inspect_config_value_access {
static std::string type_name() {
return to_string(type_name_or_anonymous<T>());
}
static optional<T> get_if(const config_value* x) {
config_value_reader reader{x};
auto tmp = T{};
if (detail::load(reader, tmp))
return optional<T>{std::move(tmp)};
return none;
}
static bool is(const config_value& x) {
return get_if(&x) != none;
}
static T get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
static config_value convert(const T& x) {
config_value result;
config_value_writer writer{&result};
if (!detail::save(writer, x))
CAF_RAISE_ERROR("unable to convert type to a config_value");
return result;
}
};
} // namespace detail
template <>
struct variant_inspector_traits<config_value> {
using value_type = config_value;
......@@ -1186,7 +702,7 @@ struct variant_inspector_traits<config_value> {
template <class F, class Value>
static auto visit(F&& f, Value&& x) {
return caf::visit(std::forward<F>(f), std::forward<Value>(x));
return caf::visit(std::forward<F>(f), x.get_data());
}
template <class U>
......
......@@ -36,6 +36,8 @@ class CAF_CORE_EXPORT config_list_consumer {
public:
// -- constructors, destructors, and assignment operators --------------------
config_list_consumer() = default;
config_list_consumer(const config_option_set* options,
config_consumer* parent);
......@@ -60,18 +62,20 @@ public:
template <class T>
void value(T&& x) {
xs_.emplace_back(std::forward<T>(x));
result.emplace_back(std::forward<T>(x));
}
config_value::list result;
std::string qualified_key();
private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<config_consumer*, config_list_consumer*, config_value_consumer*>
variant<none_t, config_consumer*, config_list_consumer*,
config_value_consumer*>
parent_;
config_value::list xs_;
};
/// Consumes a series of key-value pairs from an application configuration.
......
......@@ -101,21 +101,25 @@ CAF_CORE_EXPORT void parse(string_parser_state& ps, long double& x);
// -- CAF types ----------------------------------------------------------------
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv4_address& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv4_address&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv4_subnet& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv4_subnet&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv4_endpoint& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv4_endpoint&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv6_address& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv6_address&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv6_subnet& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv6_subnet&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, ipv6_endpoint& x);
CAF_CORE_EXPORT void parse(string_parser_state&, ipv6_endpoint&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, uri& x);
CAF_CORE_EXPORT void parse(string_parser_state&, uri&);
CAF_CORE_EXPORT void parse(string_parser_state& ps, config_value& x);
CAF_CORE_EXPORT void parse(string_parser_state&, config_value&);
CAF_CORE_EXPORT void parse(string_parser_state&, std::vector<config_value>&);
CAF_CORE_EXPORT void parse(string_parser_state&, dictionary<config_value>&);
// -- variadic utility ---------------------------------------------------------
......@@ -209,6 +213,7 @@ void parse(string_parser_state& ps,
x = value_type{since_epoch};
}
/*
// -- container types ----------------------------------------------------------
CAF_CORE_EXPORT void parse_element(string_parser_state& ps, std::string& x,
......@@ -304,6 +309,7 @@ void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
}
parse_element(ps, kvp.second, char_blacklist);
}
*/
// -- convenience functions ----------------------------------------------------
......
......@@ -102,6 +102,28 @@ void read_config_list(State& ps, Consumer&& consumer) {
// clang-format on
}
// Like read_config_list, but without surrounding '[]'.
template <class State, class Consumer>
void lift_config_list(State& ps, Consumer&& consumer) {
// clang-format off
start();
state(init) {
epsilon(before_value)
}
term_state(before_value) {
transition(before_value, " \t\n")
fsm_epsilon(read_config_comment(ps, consumer), before_value, '#')
fsm_epsilon(read_config_value(ps, consumer, std::true_type{}), after_value)
}
term_state(after_value) {
transition(after_value, " \t\n")
transition(before_value, ',')
fsm_epsilon(read_config_comment(ps, consumer), after_value, '#')
}
fin();
// clang-format on
}
template <bool Nested = true, class State, class Consumer>
void read_config_map(State& ps, Consumer&& consumer) {
std::string key;
......@@ -140,6 +162,7 @@ void read_config_map(State& ps, Consumer&& consumer) {
unstable_state(after_value) {
transition(after_value, " \t")
transition(had_newline, "\n")
transition_if(!Nested, after_comma, ',')
transition(await_key_name, ',')
transition_if(Nested, done, '}', consumer.end_map())
fsm_epsilon(read_config_comment(ps, consumer), had_newline, '#')
......@@ -157,6 +180,9 @@ void read_config_map(State& ps, Consumer&& consumer) {
epsilon_if(!Nested, done)
epsilon(unexpected_end_of_input)
}
term_state(after_comma) {
epsilon(await_key_name)
}
state(unexpected_end_of_input) {
// no transitions, only needed for the unstable states
}
......
......@@ -189,6 +189,13 @@ public:
return true;
} else if constexpr (std::is_same<T, char>::value) {
return value(string_view{x, strlen(x)});
} else if constexpr (std::is_same<T, void>::value) {
sep();
result_ += "*<";
auto addr = reinterpret_cast<intptr_t>(x);
result_ += std::to_string(addr);
result_ += '>';
return true;
} else {
sep();
result_ += '*';
......
......@@ -56,9 +56,8 @@ config_value get_impl(const void* ptr) {
template <class T>
config_option::meta_state* option_meta_state_instance() {
using trait = detail::config_value_access_t<T>;
static config_option::meta_state obj{sync_impl<T>, get_impl<T>,
trait::type_name()};
config_value::mapped_type_name<T>()};
return &obj;
}
......
......@@ -58,6 +58,10 @@ struct parser_state {
e = last;
}
parser_state(const parser_state&) noexcept = default;
parser_state& operator=(const parser_state&) noexcept = default;
/// Returns the null terminator when reaching the end of the string,
/// otherwise the next character.
char next() noexcept {
......
......@@ -170,6 +170,8 @@ enum class sec : uint8_t {
/// An operation failed because the callee does not implement this
/// functionality.
unsupported_operation,
/// A key lookup failed.
no_such_key = 65,
};
/// @relates sec
......
......@@ -38,8 +38,8 @@ CAF_CORE_EXPORT std::string to_string(const settings& xs);
/// Tries to retrieve the value associated to `name` from `xs`.
/// @relates config_value
CAF_CORE_EXPORT const config_value*
get_if(const settings* xs, string_view name);
CAF_CORE_EXPORT const config_value* get_if(const settings* xs,
string_view name);
/// Tries to retrieve the value associated to `name` from `xs`.
/// @relates config_value
......@@ -54,12 +54,14 @@ auto get_if(const settings* xs, string_view name) {
/// @relates config_value
template <class T>
bool holds_alternative(const settings& xs, string_view name) {
using access = detail::config_value_access_t<T>;
if (auto value = get_if(&xs, name))
return access::is(*value);
return false;
return holds_alternative<T>(*value);
else
return false;
}
/// Retrieves the value associated to `name` from `xs`.
/// @relates actor_system_config
template <class T>
T get(const settings& xs, string_view name) {
auto result = get_if<T>(&xs, name);
......@@ -67,27 +69,39 @@ T get(const settings& xs, string_view name) {
return detail::move_if_not_ptr(result);
}
template <class T, class = typename std::enable_if<
!std::is_pointer<T>::value
&& !std::is_convertible<T, string_view>::value>::type>
T get_or(const settings& xs, string_view name, T default_value) {
auto result = get_if<T>(&xs, name);
if (result)
return std::move(*result);
return default_value;
/// Retrieves the value associated to `name` from `xs` or returns `fallback`.
/// @relates actor_system_config
template <class To = get_or_auto_deduce, class Fallback>
auto get_or(const settings& xs, string_view name, Fallback&& fallback) {
if (auto ptr = get_if(&xs, name)) {
return get_or<To>(*ptr, std::forward<Fallback>(fallback));
} else if constexpr (std::is_same<To, get_or_auto_deduce>::value) {
using guide = get_or_deduction_guide<std::decay_t<Fallback>>;
return guide::convert(std::forward<Fallback>(fallback));
} else {
return To{std::forward<Fallback>(fallback)};
}
}
CAF_CORE_EXPORT std::string
get_or(const settings& xs, string_view name, string_view default_value);
/// Tries to retrieve the value associated to `name` from `xs` as an instance of
/// type `T`.
/// @relates actor_system_config
template <class T>
expected<T> get_as(const settings& xs, string_view name) {
if (auto ptr = get_if(&xs, name))
return get_as<T>(*ptr);
else
return {sec::no_such_key};
}
/// @private
CAF_CORE_EXPORT config_value&
put_impl(settings& dict, const std::vector<string_view>& path,
config_value& value);
CAF_CORE_EXPORT config_value& put_impl(settings& dict,
const std::vector<string_view>& path,
config_value& value);
/// @private
CAF_CORE_EXPORT config_value&
put_impl(settings& dict, string_view key, config_value& value);
CAF_CORE_EXPORT config_value& put_impl(settings& dict, string_view key,
config_value& value);
/// Converts `value` to a `config_value` and assigns it to `key`.
/// @param dict Dictionary of key-value pairs.
......@@ -118,7 +132,7 @@ CAF_CORE_EXPORT config_value::list& put_list(settings& xs, std::string name);
/// Inserts a new list named `name` into the dictionary `xs` and returns
/// a reference to it. Overrides existing entries with the same name.
CAF_CORE_EXPORT config_value::dictionary&
put_dictionary(settings& xs, std::string name);
CAF_CORE_EXPORT config_value::dictionary& put_dictionary(settings& xs,
std::string name);
} // namespace caf
......@@ -128,7 +128,7 @@ private:
return false;
for (const auto& lbl : labels) {
if (auto ptr = get_if<settings>(cfg, lbl.str())) {
if (auto bounds = get_if<std::vector<value_type>>(ptr, "buckets")) {
if (auto bounds = get_as<std::vector<value_type>>(*ptr, "buckets")) {
std::sort(bounds->begin(), bounds->end());
bounds->erase(std::unique(bounds->begin(), bounds->end()),
bounds->end());
......
......@@ -367,7 +367,7 @@ public:
if (auto grp = get_if<settings>(config_, prefix)) {
if (sub_settings = get_if<settings>(grp, name);
sub_settings != nullptr) {
if (auto lst = get_if<upper_bounds_list>(sub_settings, "buckets")) {
if (auto lst = get_as<upper_bounds_list>(*sub_settings, "buckets")) {
std::sort(lst->begin(), lst->end());
lst->erase(std::unique(lst->begin(), lst->end()), lst->end());
if (!lst->empty())
......
......@@ -295,10 +295,10 @@ actor_system::actor_system(actor_system_config& cfg)
hook->init(*this);
// Cache some configuration parameters for faster lookups at runtime.
using string_list = std::vector<std::string>;
if (auto lst = get_if<string_list>(&cfg,
if (auto lst = get_as<string_list>(cfg,
"caf.metrics-filters.actors.includes"))
metrics_actors_includes_ = std::move(*lst);
if (auto lst = get_if<string_list>(&cfg,
if (auto lst = get_as<string_list>(cfg,
"caf.metrics-filters.actors.excludes"))
metrics_actors_excludes_ = std::move(*lst);
if (!metrics_actors_includes_.empty())
......
......@@ -142,7 +142,7 @@ string_view config_option::type_name() const noexcept {
}
bool config_option::is_flag() const noexcept {
return type_name() == "boolean";
return type_name() == "bool";
}
bool config_option::has_flat_cli_name() const noexcept {
......
......@@ -62,6 +62,11 @@ auto no_conversions() {
return detail::make_overload(no_conversion<To, From>()...);
}
template <class T>
constexpr ptrdiff_t signed_index_of() {
return detail::tl_index_of<typename config_value::types, T>::value;
}
} // namespace
// -- constructors, destructors, and assignment operators ----------------------
......@@ -153,6 +158,10 @@ const char* config_value::type_name_at_index(size_t index) noexcept {
return type_names[index];
}
ptrdiff_t config_value::signed_index() const noexcept {
return static_cast<ptrdiff_t>(data_.index());
}
// -- utility ------------------------------------------------------------------
type_id_t config_value::type_id() const noexcept {
......@@ -321,6 +330,16 @@ expected<timespan> config_value::to_timespan() const {
return visit(f, data_);
}
expected<uri> config_value::to_uri() const {
using result_type = expected<uri>;
auto f = detail::make_overload(
no_conversions<uri, none_t, bool, integer, real, timespan,
config_value::list, config_value::dictionary>(),
[](const uri& x) { return result_type{x}; },
[](const std::string& x) { return make_uri(x); });
return visit(f, data_);
}
expected<config_value::list> config_value::to_list() const {
using result_type = expected<list>;
auto dict_to_list = [](const dictionary& dict, list& result) {
......@@ -335,18 +354,17 @@ expected<config_value::list> config_value::to_list() const {
auto f = detail::make_overload(
no_conversions<list, none_t, bool, integer, real, timespan, uri>(),
[dict_to_list](const std::string& x) {
// Check whether we can parse the string as a list. If that fails, try
// whether we can parse it as a dictionary instead (and then convert that
// to a list).
config_value::list tmp;
if (detail::parse(x, tmp, detail::require_opening_char) == none)
return result_type{std::move(tmp)};
config_value::dictionary dict;
if (detail::parse(x, dict, detail::require_opening_char) == none) {
tmp.clear();
// Check whether we can parse the string as a list. However, we also
// accept dictionaries that we convert to lists of key-value pairs. We
// need to try converting to dictionary *first*, because detail::parse for
// the list otherwise produces a list with one dictionary.
if (config_value::dictionary dict; detail::parse(x, dict) == none) {
config_value::list tmp;
dict_to_list(dict, tmp);
return result_type{std::move(tmp)};
}
if (config_value::list tmp; detail::parse(x, tmp) == none)
return result_type{std::move(tmp)};
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a list";
......@@ -383,12 +401,11 @@ expected<config_value::dictionary> config_value::to_dictionary() const {
return result_type{std::move(err)};
}
},
[](const std::string& x) {
if (dictionary tmp; detail::parse(x, tmp) == none) {
[this](const std::string& x) {
if (dictionary tmp; detail::parse(x, tmp) == none)
return result_type{std::move(tmp)};
}
if (list tmp; detail::parse(x, tmp) == none) {
config_value ls{std::move(tmp)};
if (auto lst = to_list()) {
config_value ls{std::move(*lst)};
if (auto res = ls.to_dictionary())
return res;
}
......@@ -453,14 +470,66 @@ config_value::parse_msg_impl(string_view str,
// -- related free functions ---------------------------------------------------
bool operator<(double x, const config_value& y) {
return config_value{x} < y;
}
bool operator<=(double x, const config_value& y) {
return config_value{x} <= y;
}
bool operator==(double x, const config_value& y) {
return config_value{x} == y;
}
bool operator>(double x, const config_value& y) {
return config_value{x} > y;
}
bool operator>=(double x, const config_value& y) {
return config_value{x} >= y;
}
bool operator<(const config_value& x, double y) {
return x < config_value{y};
}
bool operator<=(const config_value& x, double y) {
return x <= config_value{y};
}
bool operator==(const config_value& x, double y) {
return x == config_value{y};
}
bool operator>(const config_value& x, double y) {
return x > config_value{y};
}
bool operator>=(const config_value& x, double y) {
return x >= config_value{y};
}
bool operator<(const config_value& x, const config_value& y) {
return x.get_data() < y.get_data();
}
bool operator<=(const config_value& x, const config_value& y) {
return x.get_data() <= y.get_data();
}
bool operator==(const config_value& x, const config_value& y) {
return x.get_data() == y.get_data();
}
bool operator>(const config_value& x, const config_value& y) {
return x.get_data() > y.get_data();
}
bool operator>=(const config_value& x, const config_value& y) {
return x.get_data() >= y.get_data();
}
namespace {
void to_string_impl(std::string& str, const config_value& x);
......
......@@ -428,7 +428,7 @@ bool pull(config_value_reader& reader, T& x) {
auto& top = reader.top();
if (holds_alternative<const config_value*>(top)) {
auto ptr = get<const config_value*>(top);
if (auto val = get_if<internal_type>(ptr)) {
if (auto val = get_as<internal_type>(*ptr)) {
assign(*val);
reader.pop();
return true;
......@@ -446,7 +446,7 @@ bool pull(config_value_reader& reader, T& x) {
return false;
}
auto ptr = std::addressof(seq.current());
if (auto val = get_if<internal_type>(ptr)) {
if (auto val = get_as<internal_type>(*ptr)) {
assign(*val);
seq.advance();
return true;
......
......@@ -268,11 +268,12 @@ bool config_value_writer::begin_associative_array(size_t) {
CHECK_NOT_EMPTY();
settings* inner = nullptr;
auto f = detail::make_overload(
[this](config_value* val) {
[this, &inner](config_value* val) {
// Morph the top element into a dictionary.
auto& dict = val->as_dictionary();
dict.clear();
st_.top() = &dict;
inner = &dict;
return true;
},
[this](settings*) {
......
......@@ -43,14 +43,14 @@ config_list_consumer::config_list_consumer(config_value_consumer* parent)
}
pec config_list_consumer::end_list() {
auto f = make_overload(
[this](config_consumer* ptr) {
return ptr->value(config_value{std::move(xs_)});
},
[this](auto* ptr) {
ptr->value(config_value{std::move(xs_)});
return pec::success;
});
auto f = make_overload([](none_t) { return pec::success; },
[this](config_consumer* ptr) {
return ptr->value(config_value{std::move(result)});
},
[this](auto* ptr) {
ptr->value(config_value{std::move(result)});
return pec::success;
});
return visit(f, parent_);
}
......@@ -59,7 +59,8 @@ config_consumer config_list_consumer::begin_map() {
}
std::string config_list_consumer::qualified_key() {
auto f = make_overload([](config_value_consumer*) { return std::string{}; },
auto f = make_overload([](none_t) { return std::string{}; },
[](config_value_consumer*) { return std::string{}; },
[](auto* ptr) { return ptr->qualified_key(); });
return visit(f, parent_);
}
......
......@@ -165,17 +165,62 @@ void parse(string_parser_state& ps, uri& x) {
void parse(string_parser_state& ps, config_value& x) {
ps.skip_whitespaces();
if (ps.at_end()) {
if (!ps.at_end()) {
detail::config_value_consumer f;
parser::read_config_value(ps, f);
if (ps.code <= pec::trailing_character)
x = std::move(f.result);
} else {
ps.code = pec::unexpected_eof;
}
}
void parse(string_parser_state& ps, std::vector<config_value>& x) {
ps.skip_whitespaces();
if (!ps.at_end()) {
detail::config_list_consumer f;
auto fallback = ps;
if (ps.consume('[')) {
parser::read_config_list(ps, f);
if (ps.code == pec::success) {
x = std::move(f.result);
} else {
// Rewind parser state and try parsing again as a list without
// surrounding '[]' characters. This catches edge cases like
// '[1, 2], [3, 4]'. On error, we restore the parser state after the
// first error.
auto first_failure = ps;
ps = fallback;
f.result.clear();
parser::lift_config_list(ps, f);
if (ps.code <= pec::trailing_character)
x = std::move(f.result);
else
ps = first_failure;
}
} else {
// If the string isn't surrounded by '[]' in the first place, we call
// lift_config_list and keep it's result in any case.
parser::lift_config_list(ps, f);
if (ps.code == pec::success)
x = std::move(f.result);
}
} else {
ps.code = pec::unexpected_eof;
}
}
void parse(string_parser_state& ps, dictionary<config_value>& x) {
ps.skip_whitespaces();
if (!ps.at_end()) {
detail::config_consumer f{x};
if (ps.consume('{'))
parser::read_config_map(ps, f);
else
parser::read_config_map<false>(ps, f);
} else {
ps.code = pec::unexpected_eof;
return;
}
// Safe the string as fallback.
string_view str{ps.i, ps.e};
// Dispatch to parser.
detail::config_value_consumer f;
parser::read_config_value(ps, f);
if (ps.code <= pec::trailing_character)
x = std::move(f.result);
}
PARSE_IMPL(ipv4_address, ipv4_address)
......
......@@ -310,7 +310,7 @@ void logger::init(actor_system_config& cfg) {
return CAF_LOG_LEVEL_QUIET;
};
auto read_filter = [&cfg](string_list& var, string_view key) {
if (auto lst = get_if<string_list>(&cfg, key))
if (auto lst = get_as<string_list>(cfg, key))
var = std::move(*lst);
};
cfg_.file_verbosity = get_verbosity("caf.logger.file.verbosity");
......
......@@ -44,8 +44,7 @@ config_value bool_get_neg(const void* ptr) {
return config_value{!*static_cast<const bool*>(ptr)};
}
meta_state bool_neg_meta{bool_sync_neg, bool_get_neg,
detail::config_value_access_t<bool>::type_name()};
meta_state bool_neg_meta{bool_sync_neg, bool_get_neg, "bool"};
template <uint64_t Denominator>
error sync_timespan(void* ptr, config_value& x) {
......@@ -67,11 +66,10 @@ config_value get_timespan(const void* ptr) {
return config_value{val};
}
meta_state us_res_meta{sync_timespan<1000>, get_timespan<1000>,
detail::config_value_access_t<timespan>::type_name()};
meta_state us_res_meta{sync_timespan<1000>, get_timespan<1000>, "timespan"};
meta_state ms_res_meta{sync_timespan<1000000>, get_timespan<1000000>,
detail::config_value_access_t<timespan>::type_name()};
"timespan"};
} // namespace
......
......@@ -249,10 +249,8 @@ void abstract_coordinator::init(actor_system_config& cfg) {
namespace sr = defaults::scheduler;
max_throughput_ = get_or(cfg, "caf.scheduler.max-throughput",
sr::max_throughput);
if (auto num_workers = get_if<size_t>(&cfg, "caf.scheduler.max-threads"))
num_workers_ = *num_workers;
else
num_workers_ = default_thread_count();
num_workers_ = get_or(cfg, "caf.scheduler.max-threads",
default_thread_count());
}
actor_system::module::id_t abstract_coordinator::id() const {
......
......@@ -43,12 +43,12 @@ const config_value* get_if(const settings* xs, string_view name) {
name.substr(pos + 1));
}
std::string get_or(const settings& xs, string_view name,
string_view default_value) {
auto result = get_if<std::string>(&xs, name);
if (result)
return std::move(*result);
return std::string{default_value.begin(), default_value.end()};
expected<std::string> get_or(const settings& xs, string_view name,
const char* fallback) {
if (auto ptr = get_if(&xs, name))
return get_as<std::string>(*ptr);
else
return {std::string{fallback}};
}
config_value& put_impl(settings& dict, const std::vector<string_view>& path,
......
......@@ -33,6 +33,7 @@
#include <vector>
using namespace caf;
using namespace caf::literals;
using namespace std::string_literals;
......@@ -161,12 +162,19 @@ CAF_TEST(file input overrides defaults but CLI args always win) {
#define CHECK_SYNCED(var, value) \
do { \
CAF_CHECK_EQUAL(var, value); \
CAF_CHECK_EQUAL(get<decltype(var)>(cfg, #var), value); \
if (auto maybe_val = get_as<decltype(var)>(cfg, #var)) { \
CAF_CHECK_EQUAL(*maybe_val, value); \
} else { \
auto cv = get_if(std::addressof(cfg.content), #var); \
CAF_ERROR("expected type " \
<< config_value::mapped_type_name<decltype(var)>() \
<< ", got: " << cv->type_name()); \
} \
} while (false)
// Checks whether an entry in content(cfg) is equal to `value`.
#define CHECK_TEXT_ONLY(type, var, value) \
CAF_CHECK_EQUAL(get<type>(cfg, #var), value)
CAF_CHECK_EQUAL(get_as<type>(cfg, #var), value)
#define ADD(var) add(var, #var, "...")
......
......@@ -22,6 +22,8 @@
#include "core-test.hpp"
#include <sstream>
#include "caf/make_config_option.hpp"
#include "caf/config_value.hpp"
#include "caf/expected.hpp"
......@@ -32,6 +34,148 @@ using std::string;
namespace {
struct state;
struct baseline {
std::vector<std::string> cli;
std::string conf;
settings res;
std::function<bool(const state&)> predicate;
};
struct request_pair {
my_request first;
my_request second;
};
template <class Inspector>
bool inspect(Inspector& f, request_pair& x) {
return f.object(x).fields(f.field("first", x.first),
f.field("second", x.second));
}
struct state {
s1 my_app_s1;
std::vector<int32_t> my_app_vector;
level my_app_severity = level::trace;
my_request my_app_request;
request_pair my_app_request_pair;
config_option_set options;
state() {
config_option_adder{options, "?my.app"}
.add(my_app_s1, "s1", "")
.add(my_app_vector, "vector,v", "")
.add(my_app_severity, "severity,s", "")
.add(my_app_request, "request,r", "")
.add(my_app_request_pair, "request-pair,R", "");
config_option_adder{options, "sys"}
.add<std::string>("path,p", "")
.add<int8_t>("threads,tTd", "");
}
void run(baseline& x, size_t index) {
settings res;
std::istringstream src{x.conf};
if (auto parsed = actor_system_config::parse_config(src, options)) {
res = std::move(*parsed);
} else {
CAF_ERROR("failed to parse baseline at index " << index << ": "
<< parsed.error());
return;
}
auto [code, pos] = options.parse(res, x.cli);
if (pos != x.cli.end()) {
CAF_ERROR("failed to parse all arguments for baseline at index "
<< index << ", stopped at: " << *pos << " (" << code << ')');
return;
}
if (code != pec::success) {
CAF_ERROR("CLI arguments for baseline at index "
<< index << " failed to parse: " << code);
return;
}
if (!x.predicate(*this)) {
CAF_ERROR("predicate for baseline at index " << index << "failed! ");
return;
}
MESSAGE("all checks for baseline at index " << index << " passed");
}
};
struct fixture {
std::vector<baseline> baselines;
template <class Predicate>
void add_test(std::vector<std::string> cli, std::string conf, settings res,
Predicate f) {
baselines.emplace_back(baseline{
std::move(cli),
std::move(conf),
std::move(res),
f,
});
}
template <class Predicate>
void add_test(std::vector<std::string> cli, std::string conf, std::string res,
Predicate f) {
config_value cv_res{res};
if (auto parsed = get_as<settings>(cv_res))
add_test(std::move(cli), std::move(conf), std::move(*parsed),
std::move(f));
else
CAF_FAIL("failed to parse result settings: " << parsed.error()
<< "\nINPUT:\n"
<< res << '\n');
}
template <class Res>
void add_test(std::vector<std::string> cli, std::string conf, Res&& res) {
return add_test(std::move(cli), std::move(conf), std::forward<Res>(res),
[](const state&) { return true; });
}
fixture() {
using ivec = std::vector<int32_t>;
add_test({"-s", "error"}, "", R"_(my { app { severity = "error" } })_",
[](auto& st) {
return CHECK_EQ(st.my_app_severity, level::error);
});
add_test({"-v", "1, 2, 3"}, "", R"_(my { app { vector = [1, 2, 3] } })_",
[](auto& st) {
return CHECK_EQ(st.my_app_vector, ivec({1, 2, 3}));
});
add_test({"-v", "[1, 2, 3]"}, "", R"_(my { app { vector = [1, 2, 3] } })_");
add_test({"-v[1, 2, 3]"}, "", R"_(my { app { vector = [1, 2, 3] } })_");
add_test({"-v1, 2, 3,"}, "", R"_(my { app { vector = [1, 2, 3] } })_");
add_test({"-r", R"_({"a":1,"b":2})_"}, "",
R"_(my { app { request { a = 1, b = 2 } } })_");
add_test({"-r", R"_(a=1,b=2)_"}, "",
R"_(my { app { request { a = 1, b = 2 } } })_");
add_test({R"_(--my.app.request={a=1,b=2})_"}, "",
R"_(my { app { request { a = 1, b = 2 } } })_");
add_test({R"_(--my.app.request=a=1,b=2,)_"}, "",
R"_(my { app { request { a = 1, b = 2 } } })_");
add_test({"-R",
R"_({"first": {"a": 1, "b": 2}, "second": {"a": 3, "b": 4}})_"},
"",
R"_(my { app { request-pair { first { a = 1, b = 2 },
second { a = 3, b = 4 } } } })_");
}
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("options on the CLI override config files that override defaults") {
for (size_t index = 0; index < baselines.size(); ++index) {
state st;
st.run(baselines[index], index);
}
}
constexpr string_view category = "category";
constexpr string_view name = "name";
constexpr string_view explanation = "explanation";
......@@ -97,8 +241,6 @@ void compare(const config_option& lhs, const config_option& rhs) {
CAF_CHECK_EQUAL(lhs.full_name(), rhs.full_name());
}
} // namespace
CAF_TEST(copy constructor) {
auto one = make_config_option<int>("cat1", "one", "option 1");
auto two = one;
......@@ -165,12 +307,12 @@ CAF_TEST(type double) {
CAF_CHECK_EQUAL(unbox(read<double>("-1.0")), -1.0);
CAF_CHECK_EQUAL(unbox(read<double>("-0.1")), -0.1);
CAF_CHECK_EQUAL(read<double>("0"), 0.);
CAF_CHECK_EQUAL(read<double>("\"0.1\""), none);
CAF_CHECK_EQUAL(read<double>("\"0.1\""), none);
}
CAF_TEST(type string) {
CAF_CHECK_EQUAL(unbox(read<string>("foo")), "foo");
CAF_CHECK_EQUAL(unbox(read<string>("\"foo\"")), "foo");
CAF_CHECK_EQUAL(unbox(read<string>(R"_("foo")_")), R"_("foo")_");
}
CAF_TEST(type timespan) {
......@@ -230,3 +372,5 @@ CAF_TEST(find by long opt) {
// No options to look through.
check({}, false, false);
}
END_FIXTURE_SCOPE()
......@@ -53,10 +53,11 @@ struct fixture {
settings cfg;
auto res = opts.parse(cfg, std::move(args));
if (res.first != pec::success)
return res.first;
if (auto x = get_if<T>(&cfg, key))
return detail::move_if_not_ptr(x);
return sec::invalid_argument;
return {res.first};
else if (auto x = get_as<T>(cfg, key))
return {std::move(*x)};
else
return {sec::invalid_argument};
}
std::string key = "value";
......@@ -101,7 +102,7 @@ CAF_TEST(parse with ref syncing) {
settings cfg;
vector<string> args{"-i42",
"-f",
"1e12",
"1e2",
"-shello",
"--bar.l=[\"hello\", \"world\"]",
"-d",
......@@ -114,34 +115,20 @@ CAF_TEST(parse with ref syncing) {
CAF_FAIL("parser stopped at: " << *res.second);
CAF_MESSAGE("verify referenced values");
CAF_CHECK_EQUAL(foo_i, 42);
CAF_CHECK_EQUAL(foo_f, 1e12);
CAF_CHECK_EQUAL(foo_f, 1e2);
CAF_CHECK_EQUAL(foo_b, true);
CAF_CHECK_EQUAL(bar_s, "hello");
CAF_CHECK_EQUAL(bar_l, ls({"hello", "world"}));
CAF_CHECK_EQUAL(bar_d, ds({{"a", "a"}, {"b", "b"}}));
CAF_MESSAGE("verify dictionary content");
CAF_CHECK_EQUAL(get<int>(cfg, "foo.i"), 42);
CAF_CHECK_EQUAL(get_as<int>(cfg, "foo.i"), 42);
}
CAF_TEST(string parameters) {
opts.add<std::string>("value,v", "some value");
CAF_MESSAGE("test string option with and without quotes");
CAF_CHECK_EQUAL(read<std::string>({"--value=\"foo\\tbar\""}), "foo\tbar");
CAF_CHECK_EQUAL(read<std::string>({"--value=foobar"}), "foobar");
CAF_CHECK_EQUAL(read<std::string>({"-v", "\"foobar\""}), "foobar");
CAF_CHECK_EQUAL(read<std::string>({"-v", "foobar"}), "foobar");
CAF_CHECK_EQUAL(read<std::string>({"-v\"foobar\""}), "foobar");
CAF_CHECK_EQUAL(read<std::string>({"-vfoobar"}), "foobar");
CAF_CHECK_EQUAL(read<std::string>({"--value=\"'abc'\""}), "'abc'");
CAF_CHECK_EQUAL(read<std::string>({"--value='abc'"}), "'abc'");
CAF_CHECK_EQUAL(read<std::string>({"-v", "\"'abc'\""}), "'abc'");
CAF_CHECK_EQUAL(read<std::string>({"-v", "'abc'"}), "'abc'");
CAF_CHECK_EQUAL(read<std::string>({"-v'abc'"}), "'abc'");
CAF_CHECK_EQUAL(read<std::string>({"--value=\"123\""}), "123");
CAF_CHECK_EQUAL(read<std::string>({"--value=123"}), "123");
CAF_CHECK_EQUAL(read<std::string>({"-v", "\"123\""}), "123");
CAF_CHECK_EQUAL(read<std::string>({"-v", "123"}), "123");
CAF_CHECK_EQUAL(read<std::string>({"-v123"}), "123");
}
CAF_TEST(flat CLI options) {
......@@ -202,18 +189,19 @@ CAF_TEST(CLI arguments override defaults) {
CAF_MESSAGE("test integer lists");
ints = int_list{1, 2, 3};
cfg["bar"] = config_value{ints};
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(get_as<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>(cfg, {"--bar=[10, 20, 30]"}), none);
CAF_CHECK_EQUAL(ints, int_list({10, 20, 30}));
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_CHECK_EQUAL(get_as<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_MESSAGE("test string lists");
strings = string_list{"one", "two", "three"};
cfg["foo"] = config_value{strings};
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
CAF_CHECK_EQUAL(get_as<string_list>(cfg, "foo"),
string_list({"one", "two", "three"}));
CAF_CHECK_EQUAL(read<string_list>(cfg, {"--foo=[hello, world]"}), none);
CAF_CHECK_EQUAL(read<string_list>(cfg, {R"_(--foo=["hello", "world"])_"}),
none);
CAF_CHECK_EQUAL(strings, string_list({"hello", "world"}));
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
CAF_CHECK_EQUAL(get_as<string_list>(cfg, "foo"),
string_list({"hello", "world"}));
}
SUBTEST("without ref syncing") {
......@@ -223,15 +211,16 @@ CAF_TEST(CLI arguments override defaults) {
opts.add<int_list>("global", "bar,b", "some list");
CAF_MESSAGE("test integer lists");
cfg["bar"] = config_value{int_list{1, 2, 3}};
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(get_as<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>(cfg, {"--bar=[10, 20, 30]"}), none);
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_CHECK_EQUAL(get_as<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_MESSAGE("test string lists");
cfg["foo"] = config_value{string_list{"one", "two", "three"}};
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
CAF_CHECK_EQUAL(get_as<string_list>(cfg, "foo"),
string_list({"one", "two", "three"}));
CAF_CHECK_EQUAL(read<string_list>(cfg, {"--foo=[hello, world]"}), none);
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
CAF_CHECK_EQUAL(read<string_list>(cfg, {R"_(--foo=["hello", "world"])_"}),
none);
CAF_CHECK_EQUAL(get_as<string_list>(cfg, "foo"),
string_list({"hello", "world"}));
}
}
......@@ -240,8 +229,7 @@ CAF_TEST(CLI arguments may use custom types) {
settings cfg;
opts.add<foobar>("global", "foobar,f", "test option");
CAF_CHECK_EQUAL(read<foobar>(cfg, {"-f{foo=\"hello\",bar=\"world\"}"}), none);
auto fb = get_if<foobar>(&cfg, "foobar");
if (CAF_CHECK(fb))
if (auto fb = get_as<foobar>(cfg, "foobar"); CAF_CHECK(fb))
CAF_CHECK_EQUAL(*fb, foobar("hello", "world"));
}
......
......@@ -58,34 +58,6 @@ using list = config_value::list;
using dictionary = config_value::dictionary;
struct dictionary_builder {
dictionary dict;
template <class T>
dictionary_builder&& add(string_view key, T&& value) && {
dict.emplace(key, config_value{std::forward<T>(value)});
return std::move(*this);
}
dictionary make() && {
return std::move(dict);
}
config_value make_cv() && {
return config_value{std::move(dict)};
}
};
dictionary_builder dict() {
return {};
}
template <class... Ts>
config_value cfg_lst(Ts&&... xs) {
config_value::list lst{config_value{std::forward<Ts>(xs)}...};
return config_value{std::move(lst)};
}
struct fixture {
config_value cv_null;
config_value cv_true;
......@@ -109,6 +81,17 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(config_value_tests, fixture)
SCENARIO("default-constructed config values represent null") {
GIVEN("a default-constructed config value") {
config_value x;
THEN("its type is none and its to_string output is 'null'") {
CHECK(holds_alternative<none_t>(x));
CHECK_EQ(x.type_name(), "none"s);
CHECK_EQ(to_string(x), "null"s);
}
}
}
SCENARIO("get_as can convert config values to boolean") {
GIVEN("a config value x with value true or false") {
WHEN("using get_as with bool") {
......@@ -754,8 +737,8 @@ SCENARIO("config values can convert lists of tuples to dictionaries") {
make_config_value_list(2, "two"));
auto& dict = x.as_dictionary();
CHECK_EQ(dict.size(), 2u);
CHECK_EQ(dict["one"], 1);
CHECK_EQ(dict["2"], "two"s);
CHECK_EQ(dict["one"], config_value{1});
CHECK_EQ(dict["2"], config_value{"two"s});
}
}
}
......@@ -765,8 +748,8 @@ SCENARIO("config values can convert lists of tuples to dictionaries") {
auto x = config_value{R"_([["one", 1], [2, "two"]])_"};
auto& dict = x.as_dictionary();
CHECK_EQ(dict.size(), 2u);
CHECK_EQ(dict["one"], 1);
CHECK_EQ(dict["2"], "two"s);
CHECK_EQ(dict["one"], config_value{1});
CHECK_EQ(dict["2"], config_value{"two"s});
}
}
}
......@@ -827,284 +810,89 @@ SCENARIO("config values can parse messages") {
}
}
CAF_TEST(default_constructed) {
config_value x;
CAF_CHECK_EQUAL(holds_alternative<none_t>(x), true);
CAF_CHECK_EQUAL(x.type_name(), "none"s);
}
CAF_TEST(positive integer) {
config_value x{4200};
CAF_CHECK_EQUAL(holds_alternative<int64_t>(x), true);
CAF_CHECK_EQUAL(get<int64_t>(x), 4200);
CAF_CHECK(get_if<int64_t>(&x) != nullptr);
CAF_CHECK_EQUAL(holds_alternative<uint64_t>(x), true);
CAF_CHECK_EQUAL(get<uint64_t>(x), 4200u);
CAF_CHECK_EQUAL(get_if<uint64_t>(&x), uint64_t{4200});
CAF_CHECK_EQUAL(holds_alternative<int>(x), true);
CAF_CHECK_EQUAL(get<int>(x), 4200);
CAF_CHECK_EQUAL(get_if<int>(&x), 4200);
CAF_CHECK_EQUAL(holds_alternative<int16_t>(x), true);
CAF_CHECK_EQUAL(get<int16_t>(x), 4200);
CAF_CHECK_EQUAL(get_if<int16_t>(&x), int16_t{4200});
CAF_CHECK_EQUAL(holds_alternative<int8_t>(x), false);
CAF_CHECK_EQUAL(get_if<int8_t>(&x), caf::none);
}
CAF_TEST(negative integer) {
config_value x{-1};
CAF_CHECK_EQUAL(holds_alternative<int64_t>(x), true);
CAF_CHECK_EQUAL(get<int64_t>(x), -1);
CAF_CHECK(get_if<int64_t>(&x) != nullptr);
CAF_CHECK_EQUAL(holds_alternative<uint64_t>(x), false);
CAF_CHECK_EQUAL(get_if<uint64_t>(&x), none);
CAF_CHECK_EQUAL(holds_alternative<int>(x), true);
CAF_CHECK_EQUAL(get<int>(x), -1);
CAF_CHECK_EQUAL(get_if<int>(&x), -1);
CAF_CHECK_EQUAL(holds_alternative<int16_t>(x), true);
CAF_CHECK_EQUAL(get<int16_t>(x), -1);
CAF_CHECK_EQUAL(get_if<int16_t>(&x), int16_t{-1});
CAF_CHECK_EQUAL(holds_alternative<int8_t>(x), true);
CAF_CHECK_EQUAL(get_if<int8_t>(&x), int8_t{-1});
CAF_CHECK_EQUAL(holds_alternative<uint8_t>(x), false);
CAF_CHECK_EQUAL(get_if<uint8_t>(&x), none);
}
CAF_TEST(timespan) {
timespan ns500{500};
config_value x{ns500};
CAF_CHECK_EQUAL(holds_alternative<timespan>(x), true);
CAF_CHECK_EQUAL(get<timespan>(x), ns500);
CAF_CHECK_NOT_EQUAL(get_if<timespan>(&x), nullptr);
}
CAF_TEST(homogeneous list) {
using integer_list = std::vector<int64_t>;
auto xs = make_config_value_list(1, 2, 3);
auto ys = config_value{integer_list{1, 2, 3}};
CAF_CHECK_EQUAL(xs, ys);
CAF_CHECK_EQUAL(to_string(xs), "[1, 2, 3]");
CAF_CHECK_EQUAL(xs.type_name(), "list"s);
CAF_CHECK_EQUAL(holds_alternative<config_value::list>(xs), true);
CAF_CHECK_EQUAL(holds_alternative<integer_list>(xs), true);
CAF_CHECK_EQUAL(get<integer_list>(xs), integer_list({1, 2, 3}));
}
CAF_TEST(heterogeneous list) {
auto xs_value = make_config_value_list(1, "two", 3.0);
auto& xs = xs_value.as_list();
CAF_CHECK_EQUAL(xs_value.type_name(), "list"s);
CAF_REQUIRE_EQUAL(xs.size(), 3u);
CAF_CHECK_EQUAL(xs[0], 1);
CAF_CHECK_EQUAL(xs[1], "two"s);
CAF_CHECK_EQUAL(xs[2], 3.0);
}
CAF_TEST(convert_to_list) {
config_value x{int64_t{42}};
CAF_CHECK_EQUAL(x.type_name(), "integer"s);
CAF_CHECK_EQUAL(to_string(x), "42");
x.convert_to_list();
CAF_CHECK_EQUAL(x.type_name(), "list"s);
CAF_CHECK_EQUAL(to_string(x), "[42]");
x.convert_to_list();
CAF_CHECK_EQUAL(to_string(x), "[42]");
}
CAF_TEST(append) {
config_value x{int64_t{1}};
CAF_CHECK_EQUAL(to_string(x), "1");
x.append(config_value{int64_t{2}});
CAF_CHECK_EQUAL(to_string(x), "[1, 2]");
x.append(config_value{"foo"});
CAF_CHECK_EQUAL(to_string(x), R"__([1, 2, "foo"])__");
}
CAF_TEST(homogeneous dictionary) {
using integer_map = caf::dictionary<int64_t>;
auto xs = dict()
.add("value-1", config_value{100000})
.add("value-2", config_value{2})
.add("value-3", config_value{3})
.add("value-4", config_value{4})
.make();
integer_map ys{
{"value-1", 100000},
{"value-2", 2},
{"value-3", 3},
{"value-4", 4},
};
config_value xs_cv{xs};
if (auto val = get_if<int64_t>(&xs, "value-1"))
CAF_CHECK_EQUAL(*val, int64_t{100000});
else
CAF_FAIL("value-1 not an int64_t");
CAF_CHECK_EQUAL(get_if<int32_t>(&xs, "value-1"), int32_t{100000});
CAF_CHECK_EQUAL(get_if<int16_t>(&xs, "value-1"), none);
CAF_CHECK_EQUAL(get<int64_t>(xs, "value-1"), 100000);
CAF_CHECK_EQUAL(get<int32_t>(xs, "value-1"), 100000);
CAF_CHECK_EQUAL(get_if<integer_map>(&xs_cv), ys);
CAF_CHECK_EQUAL(get<integer_map>(xs_cv), ys);
}
CAF_TEST(heterogeneous dictionary) {
using string_list = std::vector<string>;
auto xs = dict()
.add("scheduler", dict()
.add("policy", config_value{"none"})
.add("max-threads", config_value{2})
.make_cv())
.add("nodes", dict()
.add("preload", cfg_lst("sun", "venus", "mercury",
"earth", "mars"))
.make_cv())
.make();
CAF_CHECK_EQUAL(get<string>(xs, "scheduler.policy"), "none");
CAF_CHECK_EQUAL(get<int64_t>(xs, "scheduler.max-threads"), 2);
CAF_CHECK_EQUAL(get_if<double>(&xs, "scheduler.max-threads"), nullptr);
string_list nodes{"sun", "venus", "mercury", "earth", "mars"};
CAF_CHECK_EQUAL(get<string_list>(xs, "nodes.preload"), nodes);
}
CAF_TEST(successful parsing) {
// Store the parsed value on the stack, because the unit test framework takes
// references when comparing values. Since we call get<T>() on the result of
// parse(), we would end up with a reference to a temporary.
config_value parsed;
auto parse = [&](const string& str) -> config_value& {
auto x = config_value::parse(str);
if (!x)
CAF_FAIL("cannot parse " << str << ": assumed a result but error "
<< to_string(x.error()));
parsed = std::move(*x);
return parsed;
};
using di = caf::dictionary<int>; // Dictionary-of-integers.
using ls = std::vector<string>; // List-of-strings.
using li = std::vector<int>; // List-of-integers.
using lli = std::vector<li>; // List-of-list-of-integers.
using std::chrono::milliseconds;
CAF_CHECK_EQUAL(get<int64_t>(parse("123")), 123);
CAF_CHECK_EQUAL(get<int64_t>(parse("+123")), 123);
CAF_CHECK_EQUAL(get<int64_t>(parse("-1")), -1);
CAF_CHECK_EQUAL(get<double>(parse("1.")), 1.);
CAF_CHECK_EQUAL(get<string>(parse("\"abc\"")), "abc");
CAF_CHECK_EQUAL(get<string>(parse("abc")), "abc");
CAF_CHECK_EQUAL(get<li>(parse("[1, 2, 3]")), li({1, 2, 3}));
CAF_CHECK_EQUAL(get<ls>(parse("[\"abc\", \"def\", \"ghi\"]")),
ls({"abc", "def", "ghi"}));
CAF_CHECK_EQUAL(get<lli>(parse("[[1, 2], [3]]")), lli({li{1, 2}, li{3}}));
CAF_CHECK_EQUAL(get<timespan>(parse("10ms")), milliseconds(10));
CAF_CHECK_EQUAL(get<di>(parse("{a=1,b=2}")), di({{"a", 1}, {"b", 2}}));
}
CAF_TEST(unsuccessful parsing) {
SCENARIO("config_value::parse returns an error for invalid inputs") {
auto parse = [](const string& str) {
auto x = config_value::parse(str);
if (x)
CAF_FAIL("assumed an error but got a result");
return std::move(x.error());
if (auto x = config_value::parse(str))
return error{};
else
return std::move(x.error());
};
CAF_CHECK_EQUAL(parse("10msb"), pec::trailing_character);
CAF_CHECK_EQUAL(parse("10foo"), pec::trailing_character);
CAF_CHECK_EQUAL(parse("[1,"), pec::unexpected_eof);
CAF_CHECK_EQUAL(parse("{a=,"), pec::unexpected_character);
CAF_CHECK_EQUAL(parse("{a=1,"), pec::unexpected_eof);
CAF_CHECK_EQUAL(parse("{a=1 b=2}"), pec::unexpected_character);
}
CAF_TEST(conversion to simple tuple) {
using tuple_type = std::tuple<size_t, std::string>;
config_value x{42};
x.as_list().emplace_back("hello world");
CAF_REQUIRE(holds_alternative<tuple_type>(x));
CAF_REQUIRE_NOT_EQUAL(get_if<tuple_type>(&x), none);
CAF_CHECK_EQUAL(get<tuple_type>(x),
std::make_tuple(size_t{42}, "hello world"s));
}
CAF_TEST(conversion to nested tuple) {
using inner_tuple_type = std::tuple<int, int>;
using tuple_type = std::tuple<size_t, inner_tuple_type>;
config_value x{42};
x.as_list().emplace_back(make_config_value_list(2, 40));
CAF_REQUIRE(holds_alternative<tuple_type>(x));
CAF_REQUIRE_NOT_EQUAL(get_if<tuple_type>(&x), none);
CAF_CHECK_EQUAL(get<tuple_type>(x),
std::make_tuple(size_t{42}, std::make_tuple(2, 40)));
}
CAF_TEST(conversion to std::vector) {
using list_type = std::vector<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::list) {
using list_type = std::list<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::set) {
using list_type = std::set<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::unordered_set) {
using list_type = std::unordered_set<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::map) {
using map_type = std::map<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
GIVEN("malformed input strings") {
THEN("calling config_value::parse returns the parser error code") {
CHECK_EQ(parse("10msb"), pec::trailing_character);
CHECK_EQ(parse("10foo"), pec::trailing_character);
CHECK_EQ(parse("[1,"), pec::unexpected_eof);
CHECK_EQ(parse("{a=,"), pec::unexpected_character);
CHECK_EQ(parse("{a=1,"), pec::unexpected_eof);
CHECK_EQ(parse("{a=1 b=2}"), pec::unexpected_character);
}
}
}
CAF_TEST(conversion to std::multimap) {
using map_type = std::multimap<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
CAF_TEST_FIXTURE_SCOPE_END()
CAF_TEST(conversion to std::unordered_map) {
using map_type = std::unordered_map<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
// -- end of scenario testing, here come several baseline checks for parsing ---
#define LIST_TEST(input_str, ...) \
([&] { \
config_value cv{input_str}; \
auto converted = cv.to_list(); \
if (converted) { \
auto res = make_config_value_list(__VA_ARGS__); \
CHECK_EQ(*converted, get<config_value::list>(res)); \
} else { \
CAF_CHECK_FAILED(converted.error()); \
} \
})()
CAF_TEST(list baseline testing) {
auto ls = [](auto... xs) { return make_config_value_list(std::move(xs)...); };
LIST_TEST(R"_([1, 2, 3])_", 1, 2, 3);
LIST_TEST(R"_(1, 2, 3)_", 1, 2, 3);
LIST_TEST(R"_([[1, 2], [3, 4]])_", ls(1, 2), ls(3, 4));
LIST_TEST(R"_([1, 2], [3, 4])_", ls(1, 2), ls(3, 4));
LIST_TEST(R"_([1, [2, [3, 4]]])_", 1, ls(2, ls(3, 4)));
}
#define DICT_TEST(input_str, ...) \
([&] { \
config_value cv{input_str}; \
auto converted = cv.to_dictionary(); \
if (converted) { \
auto res = config_value::dictionary{__VA_ARGS__}; \
CHECK_EQ(*converted, res); \
} else { \
CAF_CHECK_FAILED(converted.error()); \
} \
})()
CAF_TEST(conversion to std::unordered_multimap) {
using map_type = std::unordered_multimap<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
CAF_TEST(dictionary baseline testing) {
using dict = config_value::dictionary;
auto kvp = [](std::string key,
auto val) -> std::pair<const std::string, config_value> {
return {std::move(key), config_value{std::move(val)}};
};
auto ls = [](auto... xs) { return make_config_value_list(std::move(xs)...); };
// Unquoted keys.
DICT_TEST(R"_(a = 1, b = 2)_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_({a = 1, b = 2})_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_(my { app { foo = 'bar' } })_",
kvp("my", dict{kvp("app", dict{kvp("foo", "bar")})}));
// Quoted keys.
DICT_TEST(R"_("a" = 1, "b" = 2)_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_({"a" = 1, "b" = 2})_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_("my" { "app" { "foo" = 'bar' } })_",
kvp("my", dict{kvp("app", dict{kvp("foo", "bar")})}));
DICT_TEST(R"_('a' = 1, "b" = 2)_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_({'a' = 1, 'b' = 2})_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_('my' { 'app' { 'foo' = "bar" } })_",
kvp("my", dict{kvp("app", dict{kvp("foo", "bar")})}));
// JSON notation.
DICT_TEST(R"_({"a": 1, "b": 2})_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_({"my": { "app": { "foo": "bar" } }})_",
kvp("my", dict{kvp("app", dict{kvp("foo", "bar")})}));
// Key/value list.
DICT_TEST(R"_(["a", 1], ["b", 2])_", kvp("a", 1), kvp("b", 2));
DICT_TEST(R"_([["my", [ "app", [ "foo", "bar" ]]]])_",
kvp("my", ls("app", ls("foo", "bar"))));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -42,6 +42,7 @@ using i64_list = std::vector<i64>;
struct fixture {
config_value x;
settings dummy;
template <class T>
void set(const T& value) {
......@@ -50,19 +51,11 @@ struct fixture {
CAF_FAIL("failed two write to settings: " << writer.get_error());
}
template <class T>
optional<T> get(const settings& cfg, string_view key) {
if (auto ptr = get_if<T>(&cfg, key))
const settings& xs() const {
if (auto* ptr = get_if<settings>(&x))
return *ptr;
return none;
}
template <class T>
optional<T> get(string_view key) {
if (auto* xs = get_if<settings>(&x))
return get<T>(*xs, key);
else
CAF_FAIL("fixture does not contain a dictionary");
return dummy;
}
};
......@@ -72,18 +65,18 @@ CAF_TEST_FIXTURE_SCOPE(config_value_writer_tests, fixture)
CAF_TEST(structs become dictionaries) {
set(foobar{"hello", "world"});
CAF_CHECK_EQUAL(get<std::string>("foo"), "hello"s);
CAF_CHECK_EQUAL(get<std::string>("bar"), "world"s);
CAF_CHECK_EQUAL(get_as<std::string>(xs(), "foo"), "hello"s);
CAF_CHECK_EQUAL(get_as<std::string>(xs(), "bar"), "world"s);
}
CAF_TEST(nested structs become nested dictionaries) {
set(line{{10, 20, 30}, {70, 60, 50}});
CAF_CHECK_EQUAL(get<i64>("p1.x"), 10_i64);
CAF_CHECK_EQUAL(get<i64>("p1.y"), 20_i64);
CAF_CHECK_EQUAL(get<i64>("p1.z"), 30_i64);
CAF_CHECK_EQUAL(get<i64>("p2.x"), 70_i64);
CAF_CHECK_EQUAL(get<i64>("p2.y"), 60_i64);
CAF_CHECK_EQUAL(get<i64>("p2.z"), 50_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p1.x"), 10_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p1.y"), 20_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p1.z"), 30_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p2.x"), 70_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p2.y"), 60_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "p2.z"), 50_i64);
}
CAF_TEST(empty types and maps become dictionaries) {
......@@ -100,36 +93,36 @@ CAF_TEST(empty types and maps become dictionaries) {
tst.v7["two"] = 2;
tst.v7["three"] = 3;
set(tst);
CAF_CHECK_EQUAL(get<settings>("v1"), settings{});
CAF_CHECK_EQUAL(get<i64>("v2"), 42_i64);
CAF_CHECK_EQUAL(get<i64_list>("v3"), i64_list({-1, -2, -3, -4}));
if (auto v4 = get<config_value::list>("v4");
CAF_CHECK_EQUAL(v4->size(), 2u)) {
CAF_CHECK_EQUAL(get_as<settings>(xs(), "v1"), settings{});
CAF_CHECK_EQUAL(get_as<i64>(xs(), "v2"), 42_i64);
CAF_CHECK_EQUAL(get_as<i64_list>(xs(), "v3"), i64_list({-1, -2, -3, -4}));
if (auto v4 = get_as<config_value::list>(xs(), "v4");
CAF_CHECK(v4 && v4->size() == 2u)) {
if (auto v1 = v4->front(); CAF_CHECK(holds_alternative<settings>(v1))) {
auto& v1_xs = caf::get<settings>(v1);
auto& v1_xs = get<settings>(v1);
CAF_CHECK_EQUAL(get<double>(v1_xs, "content"), 0.0);
CAF_CHECK_EQUAL(get<std::string>(v1_xs, "@content-type"),
to_string(type_name_v<double>));
}
if (auto v2 = v4->back(); CAF_CHECK(holds_alternative<settings>(v2))) {
auto& v2_xs = caf::get<settings>(v2);
auto& v2_xs = get<settings>(v2);
CAF_CHECK_EQUAL(get<double>(v2_xs, "content"), 1.0);
CAF_CHECK_EQUAL(get<std::string>(v2_xs, "@content-type"),
to_string(type_name_v<double>));
}
}
CAF_CHECK_EQUAL(get<i64_list>("v5"), i64_list({10, 20}));
CAF_CHECK_EQUAL(get_as<i64_list>(xs(), "v5"), i64_list({10, 20}));
// TODO: check v6
CAF_CHECK_EQUAL(get<i64>("v7.one"), 1_i64);
CAF_CHECK_EQUAL(get<i64>("v7.two"), 2_i64);
CAF_CHECK_EQUAL(get<i64>("v7.three"), 3_i64);
CAF_CHECK_EQUAL(get<config_value::list>("v8"), config_value::list());
CAF_CHECK_EQUAL(get_as<i64>(xs(), "v7.one"), 1_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "v7.two"), 2_i64);
CAF_CHECK_EQUAL(get_as<i64>(xs(), "v7.three"), 3_i64);
CAF_CHECK_EQUAL(get_as<config_value::list>(xs(), "v8"), config_value::list());
}
CAF_TEST(custom inspect overloads may produce single values) {
auto tue = weekday::tuesday;
set(tue);
CAF_CHECK_EQUAL(x, "tuesday"s);
CAF_CHECK_EQUAL(get_as<std::string>(x), "tuesday"s);
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -4,6 +4,51 @@
#include "core-test.hpp"
std::string to_string(level lvl) {
switch (lvl) {
case level::all:
return "all";
case level::trace:
return "trace";
case level::debug:
return "debug";
case level::warning:
return "warning";
case level::error:
return "error";
default:
return "???";
}
}
bool from_string(caf::string_view str, level& lvl) {
auto set = [&](level value) {
lvl = value;
return true;
};
if (str == "all")
return set(level::all);
else if (str == "trace")
return set(level::trace);
else if (str == "debug")
return set(level::debug);
else if (str == "warning")
return set(level::warning);
else if (str == "error")
return set(level::error);
else
return false;
}
bool from_integer(uint8_t val, level& lvl) {
if (val < 5) {
lvl = static_cast<level>(val);
return true;
} else {
return false;
}
}
int main(int argc, char** argv) {
using namespace caf;
init_global_meta_objects<id_block::core_test>();
......
......@@ -150,7 +150,7 @@ struct s1 {
template <class Inspector>
bool inspect(Inspector& f, s1& x) {
return f.object(x).fields(f.field("value", x.value));
return f.apply(x.value);
}
struct s2 {
......@@ -159,7 +159,7 @@ struct s2 {
template <class Inspector>
bool inspect(Inspector& f, s2& x) {
return f.object(x).fields(f.field("value", x.value));
return f.apply(x.value);
}
struct s3 {
......@@ -171,7 +171,7 @@ struct s3 {
template <class Inspector>
bool inspect(Inspector& f, s3& x) {
return f.object(x).fields(f.field("value", x.value));
return f.apply(x.value);
}
struct test_array {
......@@ -267,21 +267,17 @@ bool inspect(Inspector& f, dummy_enum_class& x) {
return f.apply(get, set);
}
enum class level { all, trace, debug, warning, error };
enum class level : uint8_t { all, trace, debug, warning, error };
std::string to_string(level);
bool from_string(caf::string_view, level&);
bool from_integer(uint8_t, level&);
template <class Inspector>
bool inspect(Inspector& f, level& x) {
using integer_type = std::underlying_type_t<level>;
auto get = [&x] { return static_cast<integer_type>(x); };
auto set = [&x](integer_type val) {
if (val >= 0 && val <= 4) {
x = static_cast<level>(val);
return true;
} else {
return false;
}
};
return f.apply(get, set);
return caf::default_enum_inspect(f, x);
}
enum dummy_enum { de_foo, de_bar };
......
......@@ -82,12 +82,11 @@ CAF_TEST(config_consumer) {
detail::parser::read_config(res, consumer);
CAF_CHECK_EQUAL(res.code, pec::success);
CAF_CHECK_EQUAL(string_view(res.i, res.e), string_view());
CAF_CHECK_EQUAL(get<bool>(config, "is_server"), true);
CAF_CHECK_EQUAL(get<uint16_t>(config, "port"), 4242u);
CAF_CHECK_EQUAL(get<ls>(config, "nodes"), ls({"sun", "venus"}));
CAF_CHECK_EQUAL(get<string>(config, "logger.file-name"), "foobar.conf");
CAF_MESSAGE(config);
CAF_CHECK_EQUAL(get<timespan>(config, "scheduler.timing"), timespan(2000));
CAF_CHECK_EQUAL(get_as<bool>(config, "is_server"), true);
CAF_CHECK_EQUAL(get_as<uint16_t>(config, "port"), 4242u);
CAF_CHECK_EQUAL(get_as<ls>(config, "nodes"), ls({"sun", "venus"}));
CAF_CHECK_EQUAL(get_as<string>(config, "logger.file-name"), "foobar.conf");
CAF_CHECK_EQUAL(get_as<timespan>(config, "scheduler.timing"), timespan(2000));
}
CAF_TEST(simplified syntax) {
......
......@@ -171,41 +171,14 @@ CAF_TEST(strings) {
CAF_CHECK_EQUAL(read<std::string>(" \" foo\t\" "), " foo\t");
}
CAF_TEST(lists) {
using int_list = std::vector<int>;
using string_list = std::vector<std::string>;
CAF_CHECK_EQUAL(read<int_list>("1"), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>("1, 2, 3"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>("[1, 2, 3]"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<string_list>("a, b , \" c \""),
string_list({"a", "b", " c "}));
}
CAF_TEST(maps) {
using int_map = std::map<std::string, int>;
CAF_CHECK_EQUAL(read<int_map>(R"(a=1, "b" = 42)"),
int_map({{"a", 1}, {"b", 42}}));
CAF_CHECK_EQUAL(read<int_map>(R"({ a = 1 , b = 42 ,} )"),
int_map({{"a", 1}, {"b", 42}}));
}
CAF_TEST(uris) {
using uri_list = std::vector<uri>;
auto x_res = read<uri>("foo:bar");
if (x_res == none) {
if (auto x_res = read<uri>("foo:bar")) {
auto x = *x_res;
CAF_CHECK_EQUAL(x.scheme(), "foo");
CAF_CHECK_EQUAL(x.path(), "bar");
} else {
CAF_ERROR("my:path not recognized as URI");
return;
}
auto x = *x_res;
CAF_CHECK_EQUAL(x.scheme(), "foo");
CAF_CHECK_EQUAL(x.path(), "bar");
auto ls = unbox(read<uri_list>("foo:bar, <http://actor-framework.org/doc>"));
CAF_REQUIRE_EQUAL(ls.size(), 2u);
CAF_CHECK_EQUAL(ls[0].scheme(), "foo");
CAF_CHECK_EQUAL(ls[0].path(), "bar");
CAF_CHECK_EQUAL(ls[1].scheme(), "http");
CAF_CHECK_EQUAL(ls[1].authority().host, std::string{"actor-framework.org"});
CAF_CHECK_EQUAL(ls[1].path(), "doc");
}
CAF_TEST(IPv4 address) {
......
......@@ -48,7 +48,8 @@ struct bool_parser {
detail::parser::read_bool(res, f);
if (res.code == pec::success)
return f.x;
return res.code;
else
return res.code;
}
};
......@@ -61,23 +62,23 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(read_bool_tests, fixture)
CAF_TEST(valid booleans) {
CAF_CHECK_EQUAL(p("true"), true);
CAF_CHECK_EQUAL(p("false"), false);
CAF_CHECK_EQUAL(p("true"), res_t{true});
CAF_CHECK_EQUAL(p("false"), res_t{false});
}
CAF_TEST(invalid booleans) {
CAF_CHECK_EQUAL(p(""), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("t"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("tr"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("tru"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p(" true"), pec::unexpected_character);
CAF_CHECK_EQUAL(p("f"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("fa"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("fal"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("fals"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p(" false"), pec::unexpected_character);
CAF_CHECK_EQUAL(p("tr\nue"), pec::unexpected_newline);
CAF_CHECK_EQUAL(p("trues"), pec::trailing_character);
CAF_CHECK_EQUAL(p(""), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("t"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("tr"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("tru"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p(" true"), res_t{pec::unexpected_character});
CAF_CHECK_EQUAL(p("f"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("fa"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("fal"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p("fals"), res_t{pec::unexpected_eof});
CAF_CHECK_EQUAL(p(" false"), res_t{pec::unexpected_character});
CAF_CHECK_EQUAL(p("tr\nue"), res_t{pec::unexpected_newline});
CAF_CHECK_EQUAL(p("trues"), res_t{pec::trailing_character});
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -409,9 +409,10 @@ end object)_");
CAF_TEST(load inspectors support variant fields with fallbacks) {
fallback_dummy_message d;
using content_type = decltype(d.content);
d.content = std::string{"hello world"};
CAF_CHECK(inspect(f, d));
CAF_CHECK_EQUAL(d.content, 42.0);
CAF_CHECK_EQUAL(d.content, content_type{42.0});
CAF_CHECK_EQUAL(f.log, R"_(
begin object fallback_dummy_message
begin optional variant field content
......
......@@ -120,12 +120,12 @@ CAF_TEST(to_string converts messages to strings) {
CAF_CHECK_EQUAL(msg_as_string(R"__(this is a "test")__"), teststr);
CAF_CHECK_EQUAL(msg_as_string(make_tuple(1, 2, 3), 4, 5),
"message([1, 2, 3], 4, 5)");
CAF_CHECK_EQUAL(msg_as_string(s1{}), "message(s1([10, 20, 30]))");
CAF_CHECK_EQUAL(msg_as_string(s1{}), "message([10, 20, 30])");
s2 tmp;
tmp.value[0][1] = 100;
CAF_CHECK_EQUAL(msg_as_string(s2{}),
"message(s2([[1, 10], [2, 20], [3, 30], [4, 40]]))");
CAF_CHECK_EQUAL(msg_as_string(s3{}), "message(s3([1, 2, 3, 4]))");
"message([[1, 10], [2, 20], [3, 30], [4, 40]])");
CAF_CHECK_EQUAL(msg_as_string(s3{}), "message([1, 2, 3, 4])");
}
CAF_TEST(match_elements exposes element types) {
......
......@@ -120,7 +120,8 @@ CAF_TEST(message_lifetime_in_scoped_actor) {
self->send(self, msg);
CAF_CHECK_EQUAL(msg.cdata().get_reference_count(), 2u);
self->receive([&](int& value) {
CAF_CHECK_NOT_EQUAL(&value, msg.cdata().at(0));
auto addr = static_cast<void*>(&value);
CAF_CHECK_NOT_EQUAL(addr, msg.cdata().at(0));
value = 10;
});
CAF_CHECK_EQUAL(msg.get_as<int>(0), 42);
......
......@@ -93,7 +93,7 @@ CAF_TEST(requests without result) {
run_once();
expect((int, int), from(client).to(server).with(1, 2));
expect((void), from(server).to(client));
CAF_CHECK_EQUAL(*result, unit);
CAF_CHECK_EQUAL(*result, result_type{unit});
}
SUBTEST("request.await") {
auto client = sys.spawn([=](event_based_actor* self) {
......@@ -102,13 +102,13 @@ CAF_TEST(requests without result) {
run_once();
expect((int, int), from(client).to(server).with(1, 2));
expect((void), from(server).to(client));
CAF_CHECK_EQUAL(*result, unit);
CAF_CHECK_EQUAL(*result, result_type{unit});
}
SUBTEST("request.receive") {
auto res_hdl = self->request(server, infinite, 1, 2);
run();
res_hdl.receive([&] { *result = unit; }, ERROR_HANDLER);
CAF_CHECK_EQUAL(*result, unit);
CAF_CHECK_EQUAL(*result, result_type{unit});
}
}
......@@ -121,7 +121,7 @@ CAF_TEST(requests with integer result) {
run_once();
expect((int, int), from(client).to(server).with(1, 2));
expect((int), from(server).to(client).with(3));
CAF_CHECK_EQUAL(*result, 3);
CAF_CHECK_EQUAL(*result, result_type{3});
}
SUBTEST("request.await") {
auto client = sys.spawn([=](event_based_actor* self) {
......@@ -130,13 +130,13 @@ CAF_TEST(requests with integer result) {
run_once();
expect((int, int), from(client).to(server).with(1, 2));
expect((int), from(server).to(client).with(3));
CAF_CHECK_EQUAL(*result, 3);
CAF_CHECK_EQUAL(*result, result_type{3});
}
SUBTEST("request.receive") {
auto res_hdl = self->request(server, infinite, 1, 2);
run();
res_hdl.receive([&](int x) { *result = x; }, ERROR_HANDLER);
CAF_CHECK_EQUAL(*result, 3);
CAF_CHECK_EQUAL(*result, result_type{3});
}
}
......@@ -150,7 +150,7 @@ CAF_TEST(delegated request with integer result) {
expect((int, int), from(client).to(server).with(1, 2));
expect((int, int), from(client).to(worker).with(1, 2));
expect((int), from(worker).to(client).with(3));
CAF_CHECK_EQUAL(*result, 3);
CAF_CHECK_EQUAL(*result, result_type{3});
}
CAF_TEST(requesters support fan_out_request) {
......
......@@ -61,23 +61,22 @@ struct fixture {
}
};
const config_value& unpack(const settings& x, string_view key) {
auto i = x.find(key);
if (i == x.end())
CAF_FAIL("key not found in dictionary: " << key);
return i->second;
config_value unpack(const settings& x, string_view key) {
if (auto i = x.find(key); i != x.end())
return i->second;
else
return {};
}
template <class... Ts>
const config_value& unpack(const settings& x, string_view key,
const char* next_key, Ts... keys) {
auto i = x.find(key);
if (i == x.end())
CAF_FAIL("key not found in dictionary: " << key);
if (!holds_alternative<settings>(i->second))
CAF_FAIL("value is not a dictionary: " << key);
return unpack(get<settings>(i->second), {next_key, strlen(next_key)},
keys...);
config_value
unpack(const settings& x, string_view key, const char* next_key, Ts... keys) {
if (auto i = x.find(key); i == x.end())
return {};
else if (auto ptr = get_if<settings>(std::addressof(i->second)))
return unpack(*ptr, {next_key, strlen(next_key)}, keys...);
else
return {};
}
struct foobar {
......@@ -85,45 +84,12 @@ struct foobar {
int bar = 0;
};
} // namespace
namespace caf {
// Enable users to configure foobar's like this:
// my-value {
// foo = 42
// bar = 23
// }
template <>
struct config_value_access<foobar> {
static bool is(const config_value& x) {
auto dict = caf::get_if<config_value::dictionary>(&x);
if (dict != nullptr) {
return caf::get_if<int>(dict, "foo") != none
&& caf::get_if<int>(dict, "bar") != none;
}
return false;
}
static optional<foobar> get_if(const config_value* x) {
foobar result;
if (!is(*x))
return none;
const auto& dict = caf::get<config_value::dictionary>(*x);
result.foo = caf::get<int>(dict, "foo");
result.bar = caf::get<int>(dict, "bar");
return result;
}
static foobar get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
template <class Inspector>
bool inspect(Inspector& f, foobar& x) {
return f.object(x).fields(f.field("foo", x.foo), f.field("bar", x.bar));
}
} // namespace caf
} // namespace
CAF_TEST_FIXTURE_SCOPE(settings_tests, fixture)
......@@ -135,11 +101,11 @@ CAF_TEST(put) {
CAF_CHECK(x.contains("foo"));
CAF_CHECK(x.contains("logger"));
CAF_CHECK(x.contains("one"));
CAF_CHECK_EQUAL(unpack(x, "foo"), "bar"s);
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), "none"s);
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three"), "four"s);
CAF_CHECK_EQUAL(unpack(x, "foo"), config_value{"bar"s});
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), config_value{"none"s});
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three"), config_value{"four"s});
put(x, "logger.console", "trace");
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), "trace"s);
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), config_value{"trace"s});
}
CAF_TEST(put missing) {
......@@ -150,11 +116,11 @@ CAF_TEST(put missing) {
CAF_CHECK(x.contains("foo"));
CAF_CHECK(x.contains("logger"));
CAF_CHECK(x.contains("one"));
CAF_CHECK_EQUAL(unpack(x, "foo"), "bar"s);
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), "none"s);
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three"), "four"s);
CAF_CHECK_EQUAL(unpack(x, "foo"), config_value{"bar"s});
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), config_value{"none"s});
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three"), config_value{"four"s});
put_missing(x, "logger.console", "trace");
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), "none"s);
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), config_value{"none"s});
}
CAF_TEST(put list) {
......@@ -170,11 +136,12 @@ CAF_TEST(put list) {
CAF_TEST(put dictionary) {
put_dictionary(x, "logger").emplace("console", "none");
CAF_CHECK(x.contains("logger"));
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), "none"s);
CAF_CHECK_EQUAL(unpack(x, "logger", "console"), config_value{"none"s});
put_dictionary(x, "foo.bar").emplace("value", 42);
CAF_CHECK_EQUAL(unpack(x, "foo", "bar", "value"), 42);
CAF_CHECK_EQUAL(unpack(x, "foo", "bar", "value"), config_value{42});
put_dictionary(x, "one.two.three").emplace("four", "five");
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three", "four"), "five"s);
CAF_CHECK_EQUAL(unpack(x, "one", "two", "three", "four"),
config_value{"five"s});
}
CAF_TEST(get and get_if) {
......@@ -186,8 +153,8 @@ CAF_TEST(get and get_if) {
CAF_CHECK(get<std::string>(x, "logger.console") == "none"s);
CAF_CHECK(get_if(&x, "one.two.three") != nullptr);
CAF_CHECK(get_if<std::string>(&x, "one.two.three") == nullptr);
CAF_REQUIRE(get_if<int>(&x, "one.two.three") != none);
CAF_CHECK(get<int>(x, "one.two.three") == 4);
if (CAF_CHECK(get_if<int64_t>(&x, "one.two.three") != nullptr))
CAF_CHECK(get<int64_t>(x, "one.two.three") == 4);
}
CAF_TEST(get_or) {
......@@ -199,11 +166,10 @@ CAF_TEST(get_or) {
CAF_TEST(custom type) {
put(x, "my-value.foo", 42);
put(x, "my-value.bar", 24);
CAF_REQUIRE(holds_alternative<foobar>(x, "my-value"));
CAF_REQUIRE(get_if<foobar>(&x, "my-value") != caf::none);
auto fb = get<foobar>(x, "my-value");
CAF_CHECK_EQUAL(fb.foo, 42);
CAF_CHECK_EQUAL(fb.bar, 24);
if (auto fb = get_as<foobar>(x, "my-value"); CAF_CHECK(fb)) {
CAF_CHECK_EQUAL(fb->foo, 42);
CAF_CHECK_EQUAL(fb->bar, 24);
}
}
CAF_TEST(read_config accepts the to_string output of settings) {
......
......@@ -86,10 +86,10 @@ CAF_TEST(subspans) {
CAF_TEST(free iterator functions) {
auto xs = make_span(chars);
CAF_CHECK_EQUAL(xs.begin(), begin(xs));
CAF_CHECK_EQUAL(xs.cbegin(), cbegin(xs));
CAF_CHECK_EQUAL(xs.end(), end(xs));
CAF_CHECK_EQUAL(xs.cend(), cend(xs));
CAF_CHECK(xs.begin() == begin(xs));
CAF_CHECK(xs.cbegin() == cbegin(xs));
CAF_CHECK(xs.end() == end(xs));
CAF_CHECK(xs.cend() == cend(xs));
}
CAF_TEST(as bytes) {
......@@ -108,10 +108,10 @@ CAF_TEST(make_span) {
CAF_CHECK(std::equal(xs.begin(), xs.end(), chars.begin()));
CAF_CHECK(std::equal(ys.begin(), ys.end(), chars.begin()));
CAF_CHECK(std::equal(zs.begin(), zs.end(), chars.begin()));
CAF_CHECK_EQUAL(end(xs), end(ys));
CAF_CHECK_EQUAL(end(ys), end(zs));
CAF_CHECK_EQUAL(begin(xs), begin(ys));
CAF_CHECK_EQUAL(begin(ys), begin(zs));
CAF_CHECK(end(xs) == end(ys));
CAF_CHECK(end(ys) == end(zs));
CAF_CHECK(begin(xs) == begin(ys));
CAF_CHECK(begin(ys) == begin(zs));
}
CAF_TEST(spans are convertible from compatible containers) {
......
......@@ -81,8 +81,7 @@ using v20 = variant<i01, i02, i03, i04, i05, i06, i07, i08, i09, i10,
do { \
using type = std::decay_t<decltype(y)>; \
auto&& tmp = x; \
CAF_CHECK(holds_alternative<type>(tmp)); \
if (holds_alternative<type>(tmp)) \
if (CAF_CHECK(holds_alternative<type>(tmp))) \
CAF_CHECK_EQUAL(get<type>(tmp), y); \
} while (false)
......@@ -102,9 +101,6 @@ using v20 = variant<i01, i02, i03, i04, i05, i06, i07, i08, i09, i10,
CAF_TEST(copying_moving_roundtrips) {
actor_system_config cfg;
actor_system sys{cfg};
// default construction
variant<none_t> x1;
CAF_CHECK_EQUAL(x1, none);
variant<int, none_t> x2;
VARIANT_EQ(x2, 0);
v20 x3;
......@@ -129,11 +125,10 @@ CAF_TEST(constructors) {
variant<float, int, std::string> b{"bar"s};
variant<int, std::string, double> c{123};
variant<bool, uint8_t> d{uint8_t{252}};
CAF_CHECK_EQUAL(a, 42);
CAF_CHECK_EQUAL(b, "bar"s);
CAF_CHECK_EQUAL(c, 123);
CAF_CHECK_NOT_EQUAL(c, "123"s);
CAF_CHECK_EQUAL(d, uint8_t{252});
VARIANT_EQ(a, 42);
VARIANT_EQ(b, "bar"s);
VARIANT_EQ(c, 123);
VARIANT_EQ(d, uint8_t{252});
}
CAF_TEST(n_ary_visit) {
......@@ -152,8 +147,7 @@ CAF_TEST(get_if) {
CAF_CHECK_EQUAL(get_if<int>(&b), nullptr);
CAF_CHECK_NOT_EQUAL(get_if<std::string>(&b), nullptr);
CAF_MESSAGE("test get_if via unit test framework");
CAF_CHECK_NOT_EQUAL(b, 42);
CAF_CHECK_EQUAL(b, "foo"s);
VARIANT_EQ(b, "foo"s);
}
CAF_TEST(less_than) {
......
......@@ -46,7 +46,7 @@ instance::instance(abstract_broker* parent, callee& lstnr)
: tbl_(parent), this_node_(parent->system().node()), callee_(lstnr) {
CAF_ASSERT(this_node_ != none);
size_t workers;
if (auto workers_cfg = get_if<size_t>(&config(), "caf.middleman.workers"))
if (auto workers_cfg = get_as<size_t>(config(), "caf.middleman.workers"))
workers = *workers_cfg;
else
workers = std::min(3u, std::thread::hardware_concurrency() / 4u) + 1;
......@@ -250,7 +250,7 @@ void instance::write_server_handshake(execution_unit* ctx, byte_buffer& out_buf,
auto writer = make_callback([&](binary_serializer& sink) {
using string_list = std::vector<std::string>;
string_list app_ids;
if (auto ids = get_if<string_list>(&config(),
if (auto ids = get_as<string_list>(config(),
"caf.middleman.app-identifiers"))
app_ids = std::move(*ids);
else
......@@ -349,7 +349,7 @@ connection_state instance::handle(execution_unit* ctx, connection_handle hdl,
}
// Check the application ID.
string_list whitelist;
if (auto ls = get_if<string_list>(&config(),
if (auto ls = get_as<string_list>(config(),
"caf.middleman.app-identifiers"))
whitelist = std::move(*ls);
else
......
......@@ -130,7 +130,7 @@ public:
bool start(const config_value::dictionary& cfg) override {
// Read port, address and reuse flag from the config.
uint16_t port = 0;
if (auto cfg_port = get_if<uint16_t>(&cfg, "port")) {
if (auto cfg_port = get_as<uint16_t>(cfg, "port")) {
port = *cfg_port;
} else {
return false;
......
......@@ -46,3 +46,8 @@
#define REQUIRE_GE(lhs, rhs) CAF_REQUIRE_GREATER_OR_EQUAL(lhs, rhs)
#define MESSAGE(what) CAF_MESSAGE(what)
#define BEGIN_FIXTURE_SCOPE(fixture_class) \
CAF_TEST_FIXTURE_SCOPE(CAF_UNIFYN(tests), fixture_class)
#define END_FIXTURE_SCOPE() CAF_TEST_FIXTURE_SCOPE_END()
......@@ -483,6 +483,11 @@ bool check(test* parent, const char* file, size_t line, const char* expr,
return result;
}
bool check_un(bool result, const char* file, size_t line, const char* expr);
bool check_bin(bool result, const char* file, size_t line, const char* expr,
const std::string& lhs, const std::string& rhs);
} // namespace detail
} // namespace caf::test
......@@ -547,13 +552,8 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_CHECK(...) \
([](bool expr_result) { \
auto caf_check_res \
= ::caf::test::detail::check(::caf::test::engine::current_test(), \
__FILE__, __LINE__, #__VA_ARGS__, false, \
expr_result); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
return caf_check_res; \
return ::caf::test::detail::check_un(expr_result, __FILE__, __LINE__, \
#__VA_ARGS__); \
})(static_cast<bool>(__VA_ARGS__))
#define CAF_CHECK_FUNC(func, x_expr, y_expr) \
......@@ -642,32 +642,81 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
// -- CAF_CHECK* predicate family ----------------------------------------------
#define CAF_CHECK_EQUAL(x, y) CAF_CHECK_FUNC(::caf::test::equal_to, x, y)
#define CAF_CHECK_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val == y_val, __FILE__, __LINE__, \
#x_expr " == " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_NOT_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::not_equal_to, x, y)
#define CAF_CHECK_NOT_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val != y_val, __FILE__, __LINE__, \
#x_expr " != " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_LESS(x, y) CAF_CHECK_FUNC(::caf::test::less_than, x, y)
#define CAF_CHECK_LESS(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val < y_val, __FILE__, __LINE__, \
#x_expr " < " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_NOT_LESS(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::less_than>, x, y)
#define CAF_CHECK_NOT_LESS(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val < y_val), __FILE__, __LINE__, "not " #x_expr " < " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_LESS_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::less_than_or_equal, x, y)
#define CAF_CHECK_LESS_OR_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val <= y_val, __FILE__, __LINE__, \
#x_expr " <= " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_NOT_LESS_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::less_than_or_equal>, x, y)
#define CAF_CHECK_NOT_LESS_OR_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val <= y_val), __FILE__, __LINE__, "not " #x_expr " <= " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_GREATER(x, y) CAF_CHECK_FUNC(::caf::test::greater_than, x, y)
#define CAF_CHECK_GREATER(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val > y_val, __FILE__, __LINE__, \
#x_expr " > " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_NOT_GREATER(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::greater_than>, x, y)
#define CAF_CHECK_NOT_GREATER(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val > y_val), __FILE__, __LINE__, "not " #x_expr " > " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_GREATER_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::greater_than_or_equal, x, y)
#define CAF_CHECK_GREATER_OR_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin(x_val >= y_val, __FILE__, __LINE__, \
#x_expr " >= " #y_expr, \
caf::deep_to_string(x_val), \
caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
#define CAF_CHECK_NOT_GREATER_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::greater_than_or_equal>, x, y)
#define CAF_CHECK_NOT_GREATER_OR_EQUAL(x_expr, y_expr) \
([](const auto& x_val, const auto& y_val) { \
return ::caf::test::detail::check_bin( \
!(x_val >= y_val), __FILE__, __LINE__, "not " #x_expr " >= " #y_expr, \
caf::deep_to_string(x_val), caf::deep_to_string(y_val)); \
})(x_expr, y_expr)
// -- CAF_CHECK* predicate family ----------------------------------------------
......
......@@ -165,6 +165,51 @@ bool check(test* parent, const char* file, size_t line, const char* expr,
return result;
}
bool check_un(bool result, const char* file, size_t line, const char* expr) {
string_view rel_up = "../";
while (strncmp(file, rel_up.data(), rel_up.size()) == 0)
file += rel_up.size();
auto parent = engine::current_test();
auto out = logger::instance().massive();
if (result) {
out << term::green << "** " << term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset << "passed" << '\n';
parent->pass();
} else {
out << term::red << "!! " << term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::red
<< "check failed: " << expr << term::reset << '\n';
parent->fail(false);
}
engine::last_check_file(file);
engine::last_check_line(line);
return result;
}
bool check_bin(bool result, const char* file, size_t line, const char* expr,
const std::string& lhs, const std::string& rhs) {
string_view rel_up = "../";
while (strncmp(file, rel_up.data(), rel_up.size()) == 0)
file += rel_up.size();
auto parent = engine::current_test();
auto out = logger::instance().massive();
if (result) {
out << term::green << "** " << term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset << "passed" << '\n';
parent->pass();
} else {
out << term::red << "!! " << term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::red
<< "check failed: " << expr << term::reset << '\n'
<< " lhs: " << lhs << '\n'
<< " rhs: " << rhs << '\n';
parent->fail(false);
}
engine::last_check_file(file);
engine::last_check_line(line);
return result;
}
} // namespace detail
logger::stream::stream(logger& parent, logger::level lvl)
......
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