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 --------------------
......
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.
......
......@@ -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 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())
......
......@@ -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).
// 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_);
}
......
......@@ -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");
......
......@@ -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;
......@@ -170,7 +312,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) {
......@@ -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"));
}
......
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
......@@ -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