Unverified Commit dbdf9b26 authored by Noir's avatar Noir Committed by GitHub

Merge pull request #1194

Re-implement config_option using get_as
parents 3f59e8d4 f4c56936
......@@ -16,6 +16,18 @@ is based on [Keep a Changelog](https://keepachangelog.com).
and `caf.middleman.serialization-time`.
- The macro `CAF_ADD_TYPE_ID` now accepts an optional third parameter for
allowing users to override the default type name.
- The new function pair `get_as` and `get_or` model type conversions on a
`config_value`. For example, `get_as<int>(x)` would convert the content of `x`
to an `int` by either casting numeric values to `int` (with bound checks) or
trying to parse the input of `x` if it contains a string. The function
`get_or` already existed for `settings`, but we have added new overloads for
generalizing the function to `config_value` as well.
### Deprecated
- The new `get_as` and `get_or` function pair makes type conversions on a
`config_value` via `get`, `get_if`, etc. obsolete. We will retain the
STL-style interface for treating a `config_value` as a `variant`-like type.
### Changed
......@@ -23,9 +35,15 @@ is based on [Keep a Changelog](https://keepachangelog.com).
i.e., `caf-application.conf`.
- Simplify the type inspection API by removing the distinction between
`apply_object` and `apply_value`. Instead, inspectors only offer `apply` and
users may now also call `map`, `list`, `tuple` and `value` for unboxing simple
wrapper types. Furthermore, CAF no longer automatically serializes enumeration
types using their underlying value because this is fundamentally unsafe.
users may now also call `map`, `list`, and `tuple` for unboxing simple wrapper
types. Furthermore, CAF no longer automatically serializes enumeration types
using their underlying value because this is fundamentally unsafe.
- CAF no longer parses the input to string options on the command line. For
example, `my_app '--msg="hello"'` results in CAF storing `"hello"` (including
the quotes) for the config option `msg`. Previously, CAF tried to parse any
string input on the command-line that starts with quotes in the same way it
would parse strings from a config file, leading to very unintuitive results in
some cases (#1113).
### Fixed
......
......@@ -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
......@@ -36,22 +36,19 @@ public:
/// Custom vtable-like struct for delegating to type-specific functions and
/// storing type-specific information shared by several config options.
struct meta_state {
/// Checks whether a value matches the option's type.
error (*check)(const config_value&);
/// Stores a value in the given location.
void (*store)(void*, const config_value&);
/// Tries to perform this sequence of steps:
/// - Convert the config value to the type of the config option.
/// - Assign the converted value back to the config value to synchronize
/// conversions back to the caller.
/// - Store the converted value in the pointer unless it is `nullptr`.
error (*sync)(void*, config_value&);
/// Tries to extract a value from the given location. Exists for backward
/// compatibility only and will get removed with CAF 0.17.
config_value (*get)(const void*);
/// Tries to parse an input string. Stores and returns the parsed value on
/// success, returns an error otherwise.
expected<config_value> (*parse)(void*, string_view);
/// Human-readable name of the option's type.
std::string type_name;
string_view type_name;
};
// -- constructors, destructors, and assignment operators --------------------
......@@ -89,12 +86,19 @@ public:
/// Returns the full name for this config option as "<category>.<long name>".
string_view full_name() const noexcept;
/// Checks whether `x` holds a legal value for this option.
error check(const config_value& x) const;
/// Synchronizes the value of this config option with `x` and vice versa.
///
/// Tries to perform this sequence of steps:
/// - Convert the config value to the type of the config option.
/// - Assign the converted value back to the config value to synchronize
/// conversions back to the caller.
/// - Store the converted value unless this option is stateless.
error sync(config_value& x) const;
/// Stores `x` in this option unless it is stateless.
/// @pre `check(x) == none`.
void store(const config_value& x) const;
[[deprecated("use sync instead")]] error store(const config_value& x) const;
[[deprecated("use sync instead")]] expected<config_value>
parse(string_view input) const;
/// Returns a human-readable representation of this option's expected type.
string_view type_name() const noexcept;
......@@ -105,10 +109,6 @@ public:
/// Returns whether the category is optional for CLI options.
bool has_flat_cli_name() const noexcept;
/// Tries to parse an input string. Stores and returns the parsed value on
/// success, returns an error otherwise.
expected<config_value> parse(string_view input) const;
/// @private
// TODO: remove with CAF 0.17
optional<config_value> get() const;
......
......@@ -52,14 +52,10 @@ public:
config_option_adder&
add_neg(bool& ref, string_view name, string_view description);
/// For backward compatibility only. Do not use for new code!
/// @private
config_option_adder&
[[deprecated("use timespan options instead")]] config_option_adder&
add_us(size_t& ref, string_view name, string_view description);
/// For backward compatibility only. Do not use for new code!
/// @private
config_option_adder&
[[deprecated("use timespan options instead")]] config_option_adder&
add_ms(size_t& ref, string_view name, string_view description);
private:
......
This diff is collapsed.
......@@ -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.
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include "caf/config_value.hpp"
#include "caf/detail/type_traits.hpp"
namespace caf::detail {
template <class Trait>
struct dispatch_parse_cli_helper {
template <class... Ts>
auto operator()(Ts&&... xs)
-> decltype(Trait::parse_cli(std::forward<Ts>(xs)...)) {
return Trait::parse_cli(std::forward<Ts>(xs)...);
}
};
template <class Access, class T>
void dispatch_parse_cli(std::true_type, string_parser_state& ps, T& x,
const char* char_blacklist) {
Access::parse_cli(ps, x, char_blacklist);
}
template <class Access, class T>
void dispatch_parse_cli(std::false_type, string_parser_state& ps, T& x,
const char*) {
Access::parse_cli(ps, x);
}
template <class T>
void dispatch_parse_cli(string_parser_state& ps, T& x,
const char* char_blacklist) {
using access = caf::select_config_value_access_t<T>;
using helper_fun = dispatch_parse_cli_helper<access>;
using token_type
= bool_token<detail::is_callable_with<helper_fun, string_parser_state&, T&,
const char*>::value>;
token_type token;
dispatch_parse_cli<access>(token, ps, x, char_blacklist);
}
} // namespace caf::detail
......@@ -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,102 +213,6 @@ 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,
const char* char_blacklist);
template <class T>
enable_if_t<!is_pair<T>::value>
parse_element(string_parser_state& ps, T& x, const char*);
template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]);
struct require_opening_char_t {};
constexpr auto require_opening_char = require_opening_char_t{};
struct allow_omitting_opening_char_t {};
constexpr auto allow_omitting_opening_char = allow_omitting_opening_char_t{};
template <class T, class Policy = allow_omitting_opening_char_t>
enable_if_tt<is_iterable<T>>
parse(string_parser_state& ps, T& xs, Policy = {}) {
using value_type = deconst_kvp_t<typename T::value_type>;
static constexpr auto is_map_type = is_pair<value_type>::value;
static constexpr auto opening_char = is_map_type ? '{' : '[';
static constexpr auto closing_char = is_map_type ? '}' : ']';
auto out = std::inserter(xs, xs.end());
// List/map using [] or {} notation.
if (ps.consume(opening_char)) {
char char_blacklist[] = {closing_char, ',', '\0'};
do {
if (ps.consume(closing_char)) {
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
return;
}
value_type tmp;
parse_element(ps, tmp, char_blacklist);
if (ps.code > pec::trailing_character)
return;
*out++ = std::move(tmp);
} while (ps.consume(','));
if (ps.consume(closing_char)) {
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
} else {
ps.code = pec::unexpected_character;
}
return;
}
if constexpr (std::is_same<Policy, require_opening_char_t>::value) {
ps.code = pec::unexpected_character;
} else {
// An empty string simply results in an empty list/map.
if (ps.at_end())
return;
// List/map without [] or {}.
do {
char char_blacklist[] = {',', '\0'};
value_type tmp;
parse_element(ps, tmp, char_blacklist);
if (ps.code > pec::trailing_character)
return;
*out++ = std::move(tmp);
} while (ps.consume(','));
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
}
template <class T>
enable_if_t<!is_pair<T>::value>
parse_element(string_parser_state& ps, T& x, const char*) {
parse(ps, x);
}
template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]) {
static_assert(N > 0, "empty array");
// TODO: consider to guard the blacklist computation with
// `if constexpr (is_same_v<First, string>)` when switching to C++17.
char key_blacklist[N + 1];
if (N > 1)
memcpy(key_blacklist, char_blacklist, N - 1);
key_blacklist[N - 1] = '=';
key_blacklist[N] = '\0';
parse_element(ps, kvp.first, key_blacklist);
if (ps.code > pec::trailing_character)
return;
if (!ps.consume('=')) {
ps.code = pec::unexpected_character;
return;
}
parse_element(ps, kvp.second, char_blacklist);
}
// -- convenience functions ----------------------------------------------------
CAF_CORE_EXPORT
......
......@@ -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_ += '*';
......
......@@ -685,6 +685,44 @@ public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
struct has_reserve {
private:
template <class List>
static auto sfinae(List* l) -> decltype(l->reserve(10), std::true_type());
template <class U>
static auto sfinae(...) -> std::false_type;
using sfinae_type = decltype(sfinae<T>(nullptr));
public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
constexpr bool has_reserve_v = has_reserve<T>::value;
template <class T>
struct has_emplace_back {
private:
template <class List>
static auto sfinae(List* l)
-> decltype(l->emplace_back(std::declval<typename List::value_type>()),
std::true_type());
template <class U>
static auto sfinae(...) -> std::false_type;
using sfinae_type = decltype(sfinae<T>(nullptr));
public:
static constexpr bool value = sfinae_type::value;
};
template <class T>
constexpr bool has_emplace_back_v = has_emplace_back<T>::value;
template <class T>
class has_call_error_handler {
private:
......
......@@ -22,7 +22,6 @@
#include "caf/config_option.hpp"
#include "caf/config_value.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
......@@ -30,62 +29,41 @@
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
namespace caf {
namespace detail {
namespace caf::detail {
template <class T>
error check_impl(const config_value& x) {
if (holds_alternative<T>(x))
error sync_impl(void* ptr, config_value& x) {
if (auto val = get_as<T>(x)) {
if (auto err = x.assign(*val); !err) {
if (ptr)
*static_cast<T*>(ptr) = std::move(*val);
return none;
return make_error(pec::type_mismatch);
}
template <class T>
void store_impl(void* ptr, const config_value& x) {
*static_cast<T*>(ptr) = get<T>(x);
}
template <class T>
config_value get_impl(const void* ptr) {
using trait = detail::config_value_access_t<T>;
return config_value{trait::convert(*reinterpret_cast<const T*>(ptr))};
}
template <class T>
expected<config_value> parse_impl(T* ptr, string_view str) {
if (!ptr) {
T tmp;
return parse_impl(&tmp, str);
} else {
return err;
}
} else {
return std::move(val.error());
}
if constexpr (detail::has_clear_member<T>::value)
ptr->clear();
using trait = detail::config_value_access_t<T>;
string_parser_state ps{str.begin(), str.end()};
trait::parse_cli(ps, *ptr, top_level_cli_parsing);
if (ps.code != pec::success)
return make_error(ps);
return config_value{trait::convert(*ptr)};
}
CAF_CORE_EXPORT expected<config_value>
parse_impl(std::string* ptr, string_view str);
template <class T>
expected<config_value> parse_impl_delegate(void* ptr, string_view str) {
return parse_impl(reinterpret_cast<T*>(ptr), str);
config_value get_impl(const void* ptr) {
config_value result;
auto err = result.assign(*static_cast<const T*>(ptr));
static_cast<void>(err); // Safe to discard because sync() fails otherwise.
return result;
}
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{check_impl<T>, store_impl<T>,
get_impl<T>, parse_impl_delegate<T>,
trait::type_name()};
static config_option::meta_state obj{sync_impl<T>, get_impl<T>,
config_value::mapped_type_name<T>()};
return &obj;
}
} // namespace detail
} // namespace caf::detail
namespace caf {
/// Creates a config option that synchronizes with `storage`.
template <class T>
......@@ -110,12 +88,12 @@ make_negated_config_option(bool& storage, string_view category,
string_view name, string_view description);
// Reads timespans, but stores an integer representing microsecond resolution.
CAF_CORE_EXPORT config_option
[[deprecated("use timespan options instead")]] CAF_CORE_EXPORT config_option
make_us_resolution_config_option(size_t& storage, string_view category,
string_view name, string_view description);
// Reads timespans, but stores an integer representing millisecond resolution.
CAF_CORE_EXPORT config_option
[[deprecated("use timespan options instead")]] CAF_CORE_EXPORT config_option
make_ms_resolution_config_option(size_t& storage, string_view category,
string_view name, string_view description);
......
......@@ -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 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,
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())
......
......@@ -340,11 +340,10 @@ actor_system_config& actor_system_config::set_impl(string_view name,
if (opt == nullptr) {
std::cerr << "*** failed to set config parameter " << name
<< ": invalid name" << std::endl;
} else if (auto err = opt->check(value)) {
} else if (auto err = opt->sync(value)) {
std::cerr << "*** failed to set config parameter " << name << ": "
<< to_string(err) << std::endl;
} else {
opt->store(value);
auto category = opt->category();
if (category == "global")
content[opt->long_name()] = std::move(value);
......@@ -413,12 +412,13 @@ error actor_system_config::extract_config_file_path(string_list& args) {
args.erase(i);
return make_error(pec::missing_argument, std::move(str));
}
auto evalue = ptr->parse(path);
if (!evalue)
return std::move(evalue.error());
put(content, "config-file", *evalue);
ptr->store(*evalue);
config_value val{path};
if (auto err = ptr->sync(val); !err) {
put(content, "config-file", std::move(val));
return none;
} else {
return err;
}
}
const settings& content(const actor_system_config& cfg) {
......
......@@ -128,16 +128,13 @@ string_view config_option::full_name() const noexcept {
return buf_slice(buf_[0] == '?' ? 1 : 0, long_name_separator_);
}
error config_option::check(const config_value& x) const {
CAF_ASSERT(meta_->check != nullptr);
return meta_->check(x);
error config_option::sync(config_value& x) const {
return meta_->sync(value_, x);
}
void config_option::store(const config_value& x) const {
if (value_ != nullptr) {
CAF_ASSERT(meta_->store != nullptr);
meta_->store(value_, x);
}
error config_option::store(const config_value& x) const {
auto cpy = x;
return sync(cpy);
}
string_view config_option::type_name() const noexcept {
......@@ -145,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 {
......@@ -153,7 +150,11 @@ bool config_option::has_flat_cli_name() const noexcept {
}
expected<config_value> config_option::parse(string_view input) const {
return meta_->parse(value_, input);
config_value val{input};
if (auto err = sync(val))
return {std::move(err)};
else
return {std::move(val)};
}
optional<config_value> config_option::get() const {
......
......@@ -18,8 +18,11 @@
#include "caf/config_option_adder.hpp"
#include "caf/config.hpp"
#include "caf/config_option_set.hpp"
CAF_PUSH_DEPRECATED_WARNING
namespace caf {
config_option_adder::config_option_adder(config_option_set& target,
......@@ -53,3 +56,5 @@ config_option_adder& config_option_adder::add_impl(config_option&& opt) {
}
} // namespace caf
CAF_POP_WARNINGS
......@@ -137,6 +137,12 @@ auto config_option_set::parse(settings& config, argument_iterator first,
// Parses an argument.
using iter = string::const_iterator;
auto consume = [&](const config_option& opt, iter arg_begin, iter arg_end) {
auto to_pec_code = [](const error& err) {
if (err.category() == type_id_v<pec>)
return static_cast<pec>(err.code());
else
return pec::invalid_argument;
};
// Extract option name and category.
auto opt_name = opt.long_name();
auto opt_ctg = opt.category();
......@@ -144,27 +150,31 @@ auto config_option_set::parse(settings& config, argument_iterator first,
auto& entry = opt_ctg == "global" ? config : select_entry(config, opt_ctg);
// Flags only consume the current element.
if (opt.is_flag()) {
if (arg_begin != arg_end)
return pec::invalid_argument;
if (arg_begin == arg_end) {
config_value cfg_true{true};
opt.store(cfg_true);
if (auto err = opt.sync(cfg_true); !err) {
entry[opt_name] = cfg_true;
return pec::success;
} else {
if (arg_begin == arg_end)
return pec::missing_argument;
auto slice_size = static_cast<size_t>(std::distance(arg_begin, arg_end));
string_view slice{&*arg_begin, slice_size};
auto val = opt.parse(slice);
if (!val) {
auto& err = val.error();
if (err.category() == type_id_v<pec>)
return static_cast<pec>(err.code());
return pec::invalid_argument;
return to_pec_code(err);
}
opt.store(*val);
entry[opt_name] = std::move(*val);
} else {
return pec::invalid_argument;
}
} else {
if (arg_begin != arg_end) {
auto arg_size = static_cast<size_t>(std::distance(arg_begin, arg_end));
config_value val{string_view{std::addressof(*arg_begin), arg_size}};
if (auto err = opt.sync(val); !err) {
entry[opt_name] = std::move(val);
return pec::success;
} else {
return to_pec_code(err);
}
} else {
return pec::missing_argument;
}
}
};
// We loop over the first N-1 values, because we always consider two
// arguments at once.
......
......@@ -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).
// 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;
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();
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,12 +43,12 @@ config_list_consumer::config_list_consumer(config_value_consumer* parent)
}
pec config_list_consumer::end_list() {
auto f = make_overload(
auto f = make_overload([](none_t) { return pec::success; },
[this](config_consumer* ptr) {
return ptr->value(config_value{std::move(xs_)});
return ptr->value(config_value{std::move(result)});
},
[this](auto* ptr) {
ptr->value(config_value{std::move(xs_)});
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_);
}
......@@ -156,16 +157,11 @@ void merge_into_place(settings& src, settings& dst) {
} // namespace
pec config_consumer::value_impl(config_value&& x) {
// See whether there's a config_option associated to this key and perform a
// type check if necessary.
const config_option* opt;
if (options_ == nullptr) {
opt = nullptr;
} else {
opt = options_->qualified_name_lookup(category_, current_key_);
if (opt && opt->check(x) != none)
// Sync with config option object if available.
if (options_ != nullptr)
if (auto opt = options_->qualified_name_lookup(category_, current_key_))
if (auto err = opt->sync(x))
return pec::type_mismatch;
}
// Insert / replace value in the map.
if (auto dict = get_if<settings>(&x)) {
// Merge values into the destination, because it can already contain any
......@@ -178,13 +174,6 @@ pec config_consumer::value_impl(config_value&& x) {
} else {
cfg_->insert_or_assign(current_key_, std::move(x));
}
// Sync with config option if needed.
if (opt) {
if (auto i = cfg_->find(current_key_); i != cfg_->end())
opt->store(i->second);
else
return pec::invalid_state;
}
return pec::success;
}
......
......@@ -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()) {
ps.code = pec::unexpected_eof;
return;
}
// Safe the string as fallback.
string_view str{ps.i, ps.e};
// Dispatch to parser.
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;
}
}
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");
......
......@@ -23,73 +23,42 @@
#include "caf/config_value.hpp"
#include "caf/optional.hpp"
#define DEFAULT_META(type, parse_fun) \
config_option::meta_state type##_meta_state{ \
default_config_option_check<type>, default_config_option_store<type>, \
get_impl<type>, parse_fun, \
detail::config_value_access_t<type>::type_name()};
using std::string;
namespace caf {
namespace detail {
expected<config_value> parse_impl(std::string* ptr, string_view str) {
// Parse quoted strings, otherwise consume the entire string.
auto e = str.end();
auto i = std::find_if(str.begin(), e, [](char c) { return !isspace(c); });
if (i == e) {
if (ptr != nullptr)
ptr->assign(i, e);
return config_value{std::string{i, e}};
}
if (*i == '"') {
if (ptr == nullptr) {
std::string tmp;
if (auto err = parse(str, tmp))
return err;
return config_value{std::move(tmp)};
} else {
if (auto err = parse(str, *ptr))
return err;
return config_value{*ptr};
}
}
if (ptr != nullptr)
ptr->assign(str.begin(), str.end());
return config_value{std::string{str.begin(), str.end()}};
}
} // namespace detail
namespace {
using meta_state = config_option::meta_state;
void bool_store_neg(void* ptr, const config_value& x) {
*static_cast<bool*>(ptr) = !get<bool>(x);
error bool_sync_neg(void* ptr, config_value& x) {
if (auto val = get_as<bool>(x)) {
x = config_value{*val};
if (ptr)
*static_cast<bool*>(ptr) = !*val;
return none;
} else {
return std::move(val.error());
}
}
config_value bool_get_neg(const void* ptr) {
return config_value{!*static_cast<const bool*>(ptr)};
}
meta_state bool_neg_meta{detail::check_impl<bool>, bool_store_neg, bool_get_neg,
nullptr,
detail::config_value_access_t<bool>::type_name()};
error check_timespan(const config_value& x) {
if (holds_alternative<timespan>(x))
return none;
return make_error(pec::type_mismatch);
}
meta_state bool_neg_meta{bool_sync_neg, bool_get_neg, "bool"};
template <uint64_t Denominator>
void store_timespan(void* ptr, const config_value& x) {
error sync_timespan(void* ptr, config_value& x) {
if (auto val = get_as<timespan>(x)) {
x = config_value{*val};
if (ptr)
*static_cast<size_t*>(ptr) = static_cast<size_t>(get<timespan>(x).count())
/ Denominator;
return none;
} else {
return std::move(val.error());
}
}
template <uint64_t Denominator>
config_value get_timespan(const void* ptr) {
auto ival = static_cast<int64_t>(*static_cast<const size_t*>(ptr));
......@@ -97,13 +66,10 @@ config_value get_timespan(const void* ptr) {
return config_value{val};
}
meta_state us_res_meta{check_timespan, store_timespan<1000>, get_timespan<1000>,
nullptr,
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{check_timespan, store_timespan<1000000>,
get_timespan<1000000>, nullptr,
detail::config_value_access_t<timespan>::type_name()};
meta_state ms_res_meta{sync_timespan<1000000>, get_timespan<1000000>,
"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 {
......
......@@ -146,6 +146,8 @@ std::string to_string(sec x) {
return "type_clash";
case sec::unsupported_operation:
return "unsupported_operation";
case sec::no_such_key:
return "no_such_key";
};
}
......@@ -345,6 +347,9 @@ bool from_string(string_view in, sec& out) {
} else if (in == "unsupported_operation") {
out = sec::unsupported_operation;
return true;
} else if (in == "no_such_key") {
out = sec::no_such_key;
return true;
} else {
return false;
}
......@@ -421,6 +426,7 @@ bool from_integer(std::underlying_type_t<sec> in,
case sec::connection_closed:
case sec::type_clash:
case sec::unsupported_operation:
case sec::no_such_key:
out = result;
return true;
};
......
......@@ -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,155 @@ 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>("query,q", "")
.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 } } } })_");
add_test({}, "sys{threads=2}", R"_(sys { threads = 2 })_");
add_test({"-t", "1"}, "sys{threads=2}", R"_(sys { threads = 1 })_");
add_test({"-T", "1"}, "sys{threads=2}", R"_(sys { threads = 1 })_");
add_test({"-d", "1"}, "sys{threads=2}", R"_(sys { threads = 1 })_");
add_test({"--sys.threads=1"}, "sys{threads=2}", R"_(sys { threads = 1 })_");
add_test({"--sys.query=foo"}, "", R"_(sys { query = "foo" })_");
add_test({"-q", "\"a\" in b"}, "", R"_(sys { query = "\"a\" in b" })_");
}
};
} // 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";
......@@ -48,13 +199,12 @@ constexpr int64_t underflow() {
template <class T>
optional<T> read(string_view arg) {
auto co = make_config_option<T>(category, name, explanation);
auto res = co.parse(arg);
if (res && holds_alternative<T>(*res)) {
if (co.check(*res) != none)
CAF_ERROR("co.parse() produced the wrong type!");
return get<T>(*res);
}
auto result = T{};
auto co = make_config_option<T>(result, category, name, explanation);
config_value val{arg};
if (auto err = co.sync(val); !err)
return {std::move(result)};
else
return none;
}
......@@ -98,8 +248,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;
......@@ -171,7 +319,7 @@ CAF_TEST(type double) {
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) {
......@@ -231,3 +379,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"));
}
......
This diff is collapsed.
......@@ -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) {
CAF_ERROR("my:path not recognized as URI");
return;
}
if (auto x_res = read<uri>("foo:bar")) {
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");
} else {
CAF_ERROR("my:path not recognized as URI");
}
}
CAF_TEST(IPv4 address) {
......
......@@ -48,6 +48,7 @@ struct bool_parser {
detail::parser::read_bool(res, f);
if (res.code == pec::success)
return f.x;
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);
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
......@@ -546,18 +551,13 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
} while (false)
#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; \
})(static_cast<bool>(__VA_ARGS__))
[](bool expr_result) { \
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) \
([](auto&& x_val, auto&& y_val) { \
[](auto&& x_val, auto&& y_val) { \
func comparator; \
auto caf_check_res \
= ::caf::test::detail::check(::caf::test::engine::current_test(), \
......@@ -567,7 +567,7 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
return caf_check_res; \
})(x_expr, y_expr)
}(x_expr, y_expr)
#define CAF_CHECK_FAIL(...) \
do { \
......@@ -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_NOT_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::not_equal_to, x, y)
#define CAF_CHECK_LESS(x, y) CAF_CHECK_FUNC(::caf::test::less_than, x, y)
#define CAF_CHECK_NOT_LESS(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::less_than>, x, y)
#define CAF_CHECK_LESS_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::less_than_or_equal, x, y)
#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_GREATER(x, y) CAF_CHECK_FUNC(::caf::test::greater_than, x, y)
#define CAF_CHECK_NOT_GREATER(x, y) \
CAF_CHECK_FUNC(::caf::test::negated<::caf::test::greater_than>, x, y)
#define CAF_CHECK_GREATER_OR_EQUAL(x, y) \
CAF_CHECK_FUNC(::caf::test::greater_than_or_equal, x, y)
#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_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_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_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_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_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_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_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_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_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_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