Commit f39e3fea authored by Dominik Charousset's avatar Dominik Charousset

Fix build on GCC/Linux

parent 7a77a40e
...@@ -30,7 +30,7 @@ IndentCaseLabels: true ...@@ -30,7 +30,7 @@ IndentCaseLabels: true
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
IndentWidth: 2 IndentWidth: 2
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp Language: Cpp
MacroBlockBegin: "^BEGIN_STATE$|CAF_BEGIN_TYPE_ID_BLOCK" MacroBlockBegin: "^BEGIN_STATE$|CAF_BEGIN_TYPE_ID_BLOCK"
MacroBlockEnd: "^END_STATE$|CAF_END_TYPE_ID_BLOCK" MacroBlockEnd: "^END_STATE$|CAF_END_TYPE_ID_BLOCK"
......
...@@ -90,6 +90,7 @@ config = [ ...@@ -90,6 +90,7 @@ config = [
tags: ['docker', 'LeakSanitizer'], tags: ['docker', 'LeakSanitizer'],
builds: ['debug'], builds: ['debug'],
extraBuildFlags: [ extraBuildFlags: [
'BUILD_SHARED_LIBS:BOOL=OFF',
'CAF_LOG_LEVEL:STRING=TRACE', 'CAF_LOG_LEVEL:STRING=TRACE',
'CAF_ENABLE_ROBOT_TESTS:BOOL=ON', 'CAF_ENABLE_ROBOT_TESTS:BOOL=ON',
'CAF_SANITIZERS:STRING=address', 'CAF_SANITIZERS:STRING=address',
...@@ -104,9 +105,9 @@ config = [ ...@@ -104,9 +105,9 @@ config = [
tags: ['docker', 'UBSanitizer'], tags: ['docker', 'UBSanitizer'],
builds: ['debug'], builds: ['debug'],
extraBuildFlags: [ extraBuildFlags: [
'BUILD_SHARED_LIBS:BOOL=OFF',
'CAF_LOG_LEVEL:STRING=TRACE', 'CAF_LOG_LEVEL:STRING=TRACE',
'CAF_ENABLE_ROBOT_TESTS:BOOL=ON', 'CAF_ENABLE_ROBOT_TESTS:BOOL=ON',
'BUILD_SHARED_LIBS:BOOL=OFF',
'CAF_SANITIZERS:STRING=address,undefined', 'CAF_SANITIZERS:STRING=address,undefined',
], ],
extraBuildEnv: [ extraBuildEnv: [
......
...@@ -17,7 +17,9 @@ namespace caf::test { ...@@ -17,7 +17,9 @@ namespace caf::test {
/// structure. /// structure.
class CAF_TEST_EXPORT block { class CAF_TEST_EXPORT block {
public: public:
block(context_ptr ctx, int id, std::string_view description, // Note: the context owns the block. Hence, we can use a raw pointer for
// pointing back to the parent object here.
block(context* ctx, int id, std::string_view description,
const detail::source_location& loc); const detail::source_location& loc);
virtual ~block(); virtual ~block();
...@@ -100,7 +102,7 @@ public: ...@@ -100,7 +102,7 @@ public:
protected: protected:
std::unique_ptr<block>& get_nested_or_construct(int id); std::unique_ptr<block>& get_nested_or_construct(int id);
context_ptr ctx_; context* ctx_;
int id_ = 0; int id_ = 0;
std::string_view description_; std::string_view description_;
bool active_ = false; bool active_ = false;
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
namespace caf::test { namespace caf::test {
/// Represents the exeuction context of a test. The context stores all steps of /// Represents the execution context of a test. The context stores all steps of
/// the test and the current execution stack. The context persists across /// the test and the current execution stack. The context persists across
/// multiple runs of the test in order to select one execution path per run. /// multiple runs of the test in order to select one execution path per run.
class CAF_TEST_EXPORT context : public std::enable_shared_from_this<context> { class CAF_TEST_EXPORT context {
public: public:
/// Returns whether the test is still active. A test is active as long as /// Returns whether the test is still active. A test is active as long as
/// no unwinding is in progress. /// no unwinding is in progress.
...@@ -42,9 +42,7 @@ public: ...@@ -42,9 +42,7 @@ public:
/// Checks whether `ptr` has been activated this run, i.e., whether we can /// Checks whether `ptr` has been activated this run, i.e., whether we can
/// find it in `unwind_stack`. /// find it in `unwind_stack`.
bool activated(block* ptr) { bool activated(block* ptr) const noexcept;
return std::find(path.begin(), path.end(), ptr) != path.end();
}
/// Stores the current execution stack for the run. /// Stores the current execution stack for the run.
std::vector<block*> call_stack; std::vector<block*> call_stack;
...@@ -55,7 +53,6 @@ public: ...@@ -55,7 +53,6 @@ public:
/// Stores all steps that we have reached at least once during the run. /// Stores all steps that we have reached at least once during the run.
std::vector<block*> path; std::vector<block*> path;
/// Stores all steps of the test with their run-time ID. /// Stores all steps of the test with their run-time ID.
std::map<int, std::unique_ptr<block>> steps; std::map<int, std::unique_ptr<block>> steps;
...@@ -64,7 +61,7 @@ public: ...@@ -64,7 +61,7 @@ public:
const detail::source_location& loc) { const detail::source_location& loc) {
auto& result = steps[id]; auto& result = steps[id];
if (!result) { if (!result) {
result = std::make_unique<T>(shared_from_this(), id, description, loc); result = std::make_unique<T>(this, id, description, loc);
} }
return static_cast<T*>(result.get()); return static_cast<T*>(result.get());
} }
......
...@@ -87,4 +87,4 @@ private: ...@@ -87,4 +87,4 @@ private:
detail::source_location loc_; detail::source_location loc_;
}; };
} // namespace caf::test } // namespace caf::test
...@@ -18,7 +18,6 @@ namespace caf::test { ...@@ -18,7 +18,6 @@ namespace caf::test {
/// A registry for our factories. /// A registry for our factories.
class registry { class registry {
public: public:
/// Maps test names to factories. Elements are sorted by the order of their /// Maps test names to factories. Elements are sorted by the order of their
/// registration. /// registration.
using tests_map = unordered_flat_map<std::string_view, factory*>; using tests_map = unordered_flat_map<std::string_view, factory*>;
......
...@@ -16,7 +16,7 @@ public: ...@@ -16,7 +16,7 @@ public:
static constexpr block_type type_token = block_type::when; static constexpr block_type type_token = block_type::when;
then* get_then(int id, std::string_view description, then* get_then(int id, std::string_view description,
const detail::source_location& loc) override ; const detail::source_location& loc) override;
and_then* get_and_then(int id, std::string_view description, and_then* get_and_then(int id, std::string_view description,
const detail::source_location& loc) override; const detail::source_location& loc) override;
......
...@@ -7,9 +7,11 @@ ...@@ -7,9 +7,11 @@
#include "caf/test/context.hpp" #include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp" #include "caf/test/nesting_error.hpp"
#include <algorithm>
namespace caf::test { namespace caf::test {
block::block(context_ptr ctx, int id, std::string_view description, block::block(context* ctx, int id, std::string_view description,
const detail::source_location& loc) const detail::source_location& loc)
: ctx_(ctx), id_(id), description_(description), loc_(loc) { : ctx_(ctx), id_(id), description_(description), loc_(loc) {
// nop // nop
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include "caf/test/block.hpp" #include "caf/test/block.hpp"
#include "caf/test/reporter.hpp" #include "caf/test/reporter.hpp"
#include <algorithm>
namespace caf::test { namespace caf::test {
void context::on_enter(block* ptr) { void context::on_enter(block* ptr) {
...@@ -22,6 +24,10 @@ void context::on_leave(block* ptr) { ...@@ -22,6 +24,10 @@ void context::on_leave(block* ptr) {
reporter::instance->end_step(ptr); reporter::instance->end_step(ptr);
} }
bool context::activated(block* ptr) const noexcept {
return std::find(path.begin(), path.end(), ptr) != path.end();
}
bool context::can_run() { bool context::can_run() {
auto pred = [](auto& kvp) { return kvp.second->can_run(); }; auto pred = [](auto& kvp) { return kvp.second->can_run(); };
return !steps.empty() && std::any_of(steps.begin(), steps.end(), pred); return !steps.empty() && std::any_of(steps.begin(), steps.end(), pred);
......
...@@ -39,4 +39,4 @@ scope given::commit() { ...@@ -39,4 +39,4 @@ scope given::commit() {
return scope{this}; return scope{this};
} }
} // namespace caf::test } // namespace caf::test
...@@ -32,9 +32,9 @@ nesting_error::raise_impl(nesting_error::code what, block_type parent, ...@@ -32,9 +32,9 @@ nesting_error::raise_impl(nesting_error::code what, block_type parent,
#ifdef CAF_ENABLE_EXCEPTIONS #ifdef CAF_ENABLE_EXCEPTIONS
throw nesting_error{what, parent, child, loc}; throw nesting_error{what, parent, child, loc};
#else #else
auto msg = nesting_error{code::not_allowed, parent, child}.message(); auto msg = nesting_error{what, parent, child, loc}.message();
fprintf(stderr, "[FATAL] critical error (%s:%d): %s\n", msg.c_str(), fprintf(stderr, "[FATAL] critical error (%s:%d): %s\n", loc.file_name(),
loc.file_name, static_cast<int>(loc.line)); static_cast<int>(loc.line()), msg.c_str());
abort(); abort();
#endif #endif
} }
......
...@@ -34,6 +34,12 @@ namespace { ...@@ -34,6 +34,12 @@ namespace {
/// - $C(cyan text) /// - $C(cyan text)
/// - $0 turns off coloring completely (enter verbatim mode) /// - $0 turns off coloring completely (enter verbatim mode)
struct colorizing_iterator { struct colorizing_iterator {
using difference_type = void;
using value_type = void;
using pointer = void;
using reference = void;
using iterator_category = std::output_iterator_tag;
enum mode_t { enum mode_t {
normal, normal,
read_color, read_color,
...@@ -187,7 +193,7 @@ public: ...@@ -187,7 +193,7 @@ public:
if (!failed_suites_.empty()) { if (!failed_suites_.empty()) {
format_to(colored(), " $B(Failed Suites):\n"); format_to(colored(), " $B(Failed Suites):\n");
for (auto name : failed_suites_) for (auto name : failed_suites_)
format_to(colored(), " - $R({})\n", name); format_to(colored(), " - $R({})\n", name);
} }
std::cout << std::endl; std::cout << std::endl;
} }
...@@ -223,7 +229,7 @@ public: ...@@ -223,7 +229,7 @@ public:
if (!failed_tests_.empty()) { if (!failed_tests_.empty()) {
format_to(colored(), " $B(Failed tests):\n"); format_to(colored(), " $B(Failed tests):\n");
for (auto name : failed_tests_) for (auto name : failed_tests_)
format_to(colored(), " - $R({})\n", name); format_to(colored(), " - $R({})\n", name);
} }
std::cout << std::endl; std::cout << std::endl;
} }
...@@ -240,7 +246,7 @@ public: ...@@ -240,7 +246,7 @@ public:
failed_tests_.push_back(current_test_); failed_tests_.push_back(current_test_);
suite_stats_ += test_stats_; suite_stats_ += test_stats_;
current_ctx_.reset(); current_ctx_.reset();
if(live_) if (live_)
std::cout << std::endl; std::cout << std::endl;
} }
...@@ -252,7 +258,7 @@ public: ...@@ -252,7 +258,7 @@ public:
CAF_RAISE_ERROR(std::logic_error, "begin_test was not called"); CAF_RAISE_ERROR(std::logic_error, "begin_test was not called");
format_to(colored(), "$C(Suite): $0{}\n", current_suite_); format_to(colored(), "$C(Suite): $0{}\n", current_suite_);
indent_ = 2; indent_ = 2;
live_= true; live_ = true;
for (auto* frame : current_ctx_->call_stack) for (auto* frame : current_ctx_->call_stack)
begin_step(frame); begin_step(frame);
} }
......
...@@ -22,127 +22,142 @@ namespace caf::test { ...@@ -22,127 +22,142 @@ namespace caf::test {
namespace { namespace {
config_option_set make_option_set() { config_option_set make_option_set() {
config_option_set result; config_option_set result;
config_option_adder{result, "global"} config_option_adder{result, "global"}
.add<bool>("help,h?", "print this help text") .add<bool>("available-suites,a", "print all available suites")
.add<bool>("available-suites,a", "print all available suites") .add<bool>("help,h?", "print this help text")
.add<std::string>("available-tests,A", "print tests for a suite") .add<bool>("no-colors,n", "disable coloring (ignored on Windows)")
.add<std::string>("verbosity,v", "set verbosity level of the reporter"); .add<size_t>("max-runtime,r", "set a maximum runtime in seconds")
return result; .add<std::string>("suites,s", "regex for selecting suites")
} .add<std::string>("available-tests,A", "print tests for a suite")
.add<std::string>("verbosity,v", "set verbosity level of the reporter");
return result;
}
std::optional<unsigned> parse_log_level(std::string_view x) { std::optional<unsigned> parse_log_level(std::string_view x) {
if (x == "quiet") // Note: the 0-5 aliases are for compatibility with the old unit testing
return CAF_LOG_LEVEL_QUIET; // framework.
if (x == "error") if (x == "quiet" || x == "0")
return CAF_LOG_LEVEL_ERROR; return CAF_LOG_LEVEL_QUIET;
if (x == "warning") if (x == "error" || x == "1")
return CAF_LOG_LEVEL_WARNING; return CAF_LOG_LEVEL_ERROR;
if (x == "info") if (x == "warning" || x == "2")
return CAF_LOG_LEVEL_INFO; return CAF_LOG_LEVEL_WARNING;
if (x == "debug") if (x == "info" || x == "3")
return CAF_LOG_LEVEL_DEBUG; return CAF_LOG_LEVEL_INFO;
if (x == "trace") if (x == "debug" || x == "4")
return CAF_LOG_LEVEL_TRACE; return CAF_LOG_LEVEL_DEBUG;
return {}; if (x == "trace" || x == "5")
} return CAF_LOG_LEVEL_TRACE;
return {};
}
}// } // namespace
runner::runner() : suites_(caf::test::registry::suites()) { runner::runner() : suites_(caf::test::registry::suites()) {
// nop // nop
} }
int runner::run(int argc, char** argv) { int runner::run(int argc, char** argv) {
auto default_reporter = reporter::make_default(); auto default_reporter = reporter::make_default();
reporter::instance = default_reporter.get(); reporter::instance = default_reporter.get();
if (auto [ok, help_printed] = parse_cli(argc, argv); !ok) { if (auto [ok, help_printed] = parse_cli(argc, argv); !ok) {
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (help_printed) { } else if (help_printed) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
default_reporter->start(); default_reporter->start();
for (auto& [suite_name, suite] : suites_) { for (auto& [suite_name, suite] : suites_) {
default_reporter->begin_suite(suite_name); default_reporter->begin_suite(suite_name);
for (auto [test_name, factory_instance] : suite) { for (auto [test_name, factory_instance] : suite) {
auto state = std::make_shared<context>(); auto state = std::make_shared<context>();
try { #ifdef CAF_ENABLE_EXCEPTIONS
do { try {
default_reporter->begin_test(state, test_name); do {
auto def = factory_instance->make(state); default_reporter->begin_test(state, test_name);
def->run(); auto def = factory_instance->make(state);
default_reporter->end_test(); def->run();
state->clear_stacks();
} while (state->can_run());
} catch (const nesting_error& ex) {
default_reporter->unhandled_exception(ex.message(), ex.location());
default_reporter->end_test();
} catch (const std::exception& ex) {
default_reporter->unhandled_exception(ex.what());
default_reporter->end_test();
} catch (...) {
default_reporter->unhandled_exception("unknown exception type");
default_reporter->end_test(); default_reporter->end_test();
} state->clear_stacks();
} while (state->can_run());
} catch (const nesting_error& ex) {
default_reporter->unhandled_exception(ex.message(), ex.location());
default_reporter->end_test();
} catch (const std::exception& ex) {
default_reporter->unhandled_exception(ex.what());
default_reporter->end_test();
} catch (...) {
default_reporter->unhandled_exception("unknown exception type");
default_reporter->end_test();
} }
default_reporter->end_suite(suite_name); #else
do {
default_reporter->begin_test(state, test_name);
auto def = factory_instance->make(state);
def->run();
default_reporter->end_test();
state->clear_stacks();
} while (state->can_run());
#endif
} }
default_reporter->stop(); default_reporter->end_suite(suite_name);
return default_reporter->success() > 0 ? EXIT_SUCCESS : EXIT_FAILURE; }
default_reporter->stop();
return default_reporter->success() > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
} }
runner::parse_cli_result runner::parse_cli(int argc, char** argv) { runner::parse_cli_result runner::parse_cli(int argc, char** argv) {
using detail::format_to; using detail::format_to;
caf::settings cfg; caf::settings cfg;
std::vector<std::string> args_cpy{argv + 1, argv + argc}; std::vector<std::string> args_cpy{argv + 1, argv + argc};
auto options = make_option_set(); auto options = make_option_set();
auto res = options.parse(cfg, args_cpy); auto res = options.parse(cfg, args_cpy);
auto err = std::ostream_iterator<char>{std::cerr}; auto err = std::ostream_iterator<char>{std::cerr};
if (res.first != caf::pec::success) { if (res.first != caf::pec::success) {
format_to(err, "error while parsing argument '{}': {}\n\n{}\n", format_to(err, "error while parsing argument '{}': {}\n\n{}\n", *res.second,
*res.second, to_string(res.first), options.help_text()); to_string(res.first), options.help_text());
return {false, true};
}
if (get_or(cfg, "help", false)) {
format_to(err, "{}\n", options.help_text());
return {true, true};
}
if (get_or(cfg, "available-suites", false)) {
format_to(err, "available suites:\n");
for (auto& [suite_name, suite] : suites_)
format_to(err, "- {}\n", suite_name);
return {true, true};
}
if (auto suite_name = get_as<std::string>(cfg, "available-tests")) {
auto i = suites_.find(*suite_name);
if (i == suites_.end()) {
format_to(err, "no such suite: {}\n", *suite_name);
return {false, true}; return {false, true};
} }
if (get_or(cfg, "help", false)) { format_to(err, "available tests in suite {}:\n", i->first);
format_to(err, "{}\n", options.help_text()); for (const auto& [test_name, factory_instance] : i->second)
return {true, true}; format_to(err, "- {}\n", test_name);
} return {true, true};
if (get_or(cfg, "available-suites", false)) { }
format_to(err, "available suites:\n"); if (auto verbosity = get_as<std::string>(cfg, "verbosity")) {
for (auto& [suite_name, suite] : suites_) auto level = parse_log_level(*verbosity);
format_to(err, "- {}\n", suite_name); if (!level) {
return {true, true}; format_to(err,
} "unrecognized verbosity level: '{}'\n"
if (auto suite_name = get_as<std::string>(cfg, "available-tests")) { "expected one of:\n"
auto i = suites_.find(*suite_name); "- quiet\n"
if (i == suites_.end()) { "- error\n"
format_to(err, "no such suite: {}\n", *suite_name); "- warning\n"
return {false, true}; "- info\n"
} "- debug\n"
format_to(err, "available tests in suite {}:\n", i->first); "- trace\n",
for (const auto& [test_name, factory_instance] : i->second) *verbosity);
format_to(err, "- {}\n", test_name); return {false, true};
return {true, true};
}
if (auto verbosity = get_as<std::string>(cfg, "verbosity")) {
auto level = parse_log_level(*verbosity);
if (!level) {
format_to(err,
"unrecognized verbosity level: '{}'\n"
"expected one of:\n"
"- quiet\n"
"- error\n"
"- warning\n"
"- info\n"
"- debug\n"
"- trace\n",
*verbosity);
return {false, true};
}
reporter::instance->verbosity(*level);
} }
return {true, false}; reporter::instance->verbosity(*level);
}
return {true, false};
} }
} // namespace caf::test } // namespace caf::test
...@@ -14,35 +14,35 @@ ...@@ -14,35 +14,35 @@
namespace caf::test { namespace caf::test {
block_type scenario::type() const noexcept { block_type scenario::type() const noexcept {
return block_type::scenario; return block_type::scenario;
} }
given* scenario::get_given(int id, std::string_view description, given* scenario::get_given(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<given>(id, description, loc);
}
and_given* scenario::get_and_given(int id, std::string_view description,
const detail::source_location& loc) { const detail::source_location& loc) {
return get_nested<and_given>(id, description, loc); return get_nested<given>(id, description, loc);
} }
when* scenario::get_when(int id, std::string_view description, and_given* scenario::get_and_given(int id, std::string_view description,
const detail::source_location& loc) { const detail::source_location& loc) {
return get_nested<when>(id, description, loc); return get_nested<and_given>(id, description, loc);
} }
and_when* scenario::get_and_when(int id, std::string_view description, when* scenario::get_when(int id, std::string_view description,
const detail::source_location& loc) { const detail::source_location& loc) {
return get_nested<and_when>(id, description, loc); return get_nested<when>(id, description, loc);
} }
scope scenario::commit() { and_when* scenario::get_and_when(int id, std::string_view description,
if (!ctx_->active() || !can_run()) const detail::source_location& loc) {
return {}; return get_nested<and_when>(id, description, loc);
enter(); }
return scope{this};
} scope scenario::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test } // namespace caf::test
...@@ -44,7 +44,6 @@ SCENARIO("each run starts with fresh local variables") { ...@@ -44,7 +44,6 @@ SCENARIO("each run starts with fresh local variables") {
} }
} }
struct int_fixture { struct int_fixture {
int my_int = 0; int my_int = 0;
}; };
...@@ -105,12 +104,12 @@ SCENARIO("scenario-1") { ...@@ -105,12 +104,12 @@ SCENARIO("scenario-1") {
} }
} }
AND_WHEN("and-when-1-1") { AND_WHEN("and-when-1-1") {
check_eq(++counter, 6); check_eq(++counter, 6);
check_eq(render(), "scenario-1/given-1/and-when-1-1"); check_eq(render(), "scenario-1/given-1/and-when-1-1");
} }
AND_WHEN("and-when-1-2") { AND_WHEN("and-when-1-2") {
check_eq(++counter, 7); check_eq(++counter, 7);
check_eq(render(), "scenario-1/given-1/and-when-1-2"); check_eq(render(), "scenario-1/given-1/and-when-1-2");
} }
WHEN("when-2") { WHEN("when-2") {
check_eq(++counter, 2); check_eq(++counter, 2);
...@@ -129,12 +128,12 @@ SCENARIO("scenario-1") { ...@@ -129,12 +128,12 @@ SCENARIO("scenario-1") {
} }
} }
AND_WHEN("and-when-2-1") { AND_WHEN("and-when-2-1") {
check_eq(++counter, 6); check_eq(++counter, 6);
check_eq(render(), "scenario-1/given-1/and-when-2-1"); check_eq(render(), "scenario-1/given-1/and-when-2-1");
} }
AND_WHEN("and-when-2-2") { AND_WHEN("and-when-2-2") {
check_eq(++counter, 7); check_eq(++counter, 7);
check_eq(render(), "scenario-1/given-1/and-when-2-2"); check_eq(render(), "scenario-1/given-1/and-when-2-2");
} }
} }
} }
......
...@@ -8,7 +8,7 @@ using caf::test::block_type; ...@@ -8,7 +8,7 @@ using caf::test::block_type;
SUITE("caf.test.test") { SUITE("caf.test.test") {
TEST("tests can contain checks") { TEST("tests can contain checks") {
auto* rep = caf::test::reporter::instance; auto* rep = caf::test::reporter::instance;
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
check_eq(i, i); check_eq(i, i);
auto stats = rep->test_stats(); auto stats = rep->test_stats();
......
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