Commit 6eff7c2b authored by Dominik Charousset's avatar Dominik Charousset Committed by Marian Triebe

Refactor unit testing framework, fix CMake setup

parent 58c573fe
......@@ -279,13 +279,31 @@ add_custom_target(uninstall
# path to caf core & io headers
set(LIBCAF_INCLUDE_DIRS
<<<<<<< HEAD
"${CMAKE_CURRENT_SOURCE_DIR}/libcaf_core"
"${CMAKE_CURRENT_SOURCE_DIR}/libcaf_io")
=======
"${CMAKE_SOURCE_DIR}/libcaf_test"
"${CMAKE_SOURCE_DIR}/libcaf_core"
"${CMAKE_SOURCE_DIR}/libcaf_io")
>>>>>>> Refactor unit testing framework, fix CMake setup
# path to caf opencl headers
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libcaf_opencl/CMakeLists.txt")
set(LIBCAF_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/libcaf_opencl/" "${LIBCAF_INCLUDE_DIRS}")
endif()
# enable tests if not disabled
if(NOT CAF_NO_UNIT_TESTS)
enable_testing()
macro(add_unit_tests globstr)
file(GLOB_RECURSE tests "${globstr}")
set(CAF_ALL_UNIT_TESTS ${CAF_ALL_UNIT_TESTS} ${tests})
endmacro()
else()
macro(add_unit_tests globstr)
# do nothing (unit tests disabled)
endmacro()
endif()
# all projects need the headers of the core components
include_directories("${LIBCAF_INCLUDE_DIRS}")
......@@ -294,8 +312,14 @@ include_directories("${LIBCAF_INCLUDE_DIRS}")
# add subprojects #
################################################################################
# build libcaf_test if not being told otherwise
if(NOT CAF_NO_UNIT_TESTS)
#message(STATUS "Enter subdirectory libcaf_test")
#add_subdirectory(libcaf_test)
endif()
# core library
add_subdirectory(libcaf_core)
add_unit_tests("libcaf_core/test/*.cpp")
# set core lib for sub directories
if(NOT CAF_BUILD_STATIC_ONLY)
set(LIBCAF_CORE_LIBRARY libcaf_core_shared)
......@@ -326,15 +350,6 @@ add_dependencies(libcaf_io libcaf_core)
set(LIBCAF_LIBRARIES "${LIBCAF_CORE_LIBRARY}"
"${LIBCAF_IO_LIBRARY}"
"${LIBCAF_OPENCL_LIBRARY}")
# add unit tests if not being told otherwise
if(NOT CAF_NO_UNIT_TESTS)
enable_testing()
add_subdirectory(libcaf_test)
add_dependencies(all_unit_tests libcaf_io)
if(NOT CAF_NO_OPENCL AND EXISTS "${CMAKE_SOURCE_DIR}/libcaf_opencl/CMakeLists.txt")
add_subdirectory(libcaf_opencl/unit_testing)
endif()
endif()
# build examples if not being told otherwise
if(NOT CAF_NO_EXAMPLES)
add_subdirectory(examples)
......@@ -342,7 +357,6 @@ if(NOT CAF_NO_EXAMPLES)
if(NOT CAF_NO_OPENCL
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libcaf_opencl/CMakeLists.txt")
add_subdirectory(libcaf_opencl/examples)
add_dependencies(opencl_examples libcaf_opencl)
endif()
endif()
# build RIAC if not being told otherwise
......@@ -404,6 +418,47 @@ else()
endif()
################################################################################
# unit tests setup #
################################################################################
if(NOT CAF_NO_UNIT_TESTS)
add_executable(caf-test
libcaf_test/src/caf-test.cpp
libcaf_test/caf/test/unit_test.hpp
libcaf_test/caf/test/unit_test_impl.hpp
${CAF_ALL_UNIT_TESTS})
target_link_libraries(caf-test
${LD_FLAGS}
${LIBCAF_LIBRARIES}
${PTHREAD_LIBRARIES})
add_custom_target(all_unit_tests)
add_dependencies(caf-test all_unit_tests)
# enumerate all test suites.
foreach(test ${CAF_ALL_UNIT_TESTS})
file(STRINGS ${test} contents)
foreach(line ${contents})
if ("${line}" MATCHES "CAF_SUITE (.*)")
string(REGEX REPLACE ".* CAF_SUITE (.*)" "\\1" suite ${line})
list(APPEND suites ${suite})
endif()
endforeach()
endforeach()
list(REMOVE_DUPLICATES suites)
# creates one CMake test per test suite.
macro (make_test suite)
string(REPLACE " " "_" test_name ${suite})
set(caf_test ${EXECUTABLE_OUTPUT_PATH}/caf-test)
add_test(${test_name} ${caf_test} -n -v 5 -s "${suite}" ${ARGN})
endmacro ()
list(LENGTH suites num_suites)
message(STATUS "Found ${num_suites} test suites")
foreach(suite ${suites})
make_test("${suite}")
endforeach ()
endif()
################################################################################
# LateX setup #
################################################################################
......
#include <string>
#include <typeinfo>
#include <iostream>
#include "test.hpp"
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#include "caf/all.hpp"
#include "caf/scoped_actor.hpp"
#define CAF_SUITE atom
#include "caf/test/unit_test.hpp"
namespace caf {
inline std::ostream& operator<<(std::ostream& out, const atom_value& a) {
return (out << to_string(a));
}
} // namespace caf
#include <string>
using std::cout;
using std::endl;
using std::string;
#include "caf/all.hpp"
using namespace caf;
namespace {
constexpr auto s_foo = atom("FooBar");
using abc_atom = atom_constant<atom("abc")>;
}
using testee = typed_actor<replies_to<abc_atom>::with<int>>;
testee::behavior_type testee_impl(testee::pointer self) {
return {
[=](abc_atom) {
CAF_CHECKPOINT();
self->quit();
return 42;
}
};
}
void test_typed_atom_interface() {
CAF_CHECKPOINT();
scoped_actor self;
auto tst = spawn_typed(testee_impl);
self->sync_send(tst, abc_atom::value).await(
[](int i) {
CAF_CHECK_EQUAL(i, 42);
},
CAF_UNEXPECTED_MSG_CB_REF(self)
);
}
} // namespace <anonymous>
template <atom_value AtomValue, class... Types>
void foo() {
CAF_PRINT("foo(" << static_cast<uint64_t>(AtomValue) << " = "
<< to_string(AtomValue) << ")");
CAF_TEST(basics) {
// check if there are leading bits that distinguish "zzz" and "000 "
CAF_CHECK(atom("zzz") != atom("000 "));
// check if there are leading bits that distinguish "abc" and " abc"
CAF_CHECK(atom("abc") != atom(" abc"));
// 'illegal' characters are mapped to whitespaces
CAF_CHECK_EQUAL(atom(" "), atom("@!?"));
// check to_string impl.
CAF_CHECK_EQUAL(to_string(s_foo), "FooBar");
}
struct send_to_self {
......@@ -65,16 +55,7 @@ struct send_to_self {
blocking_actor* m_self;
};
int main() {
CAF_TEST(test_atom);
// check if there are leading bits that distinguish "zzz" and "000 "
CAF_CHECK_NOT_EQUAL(atom("zzz"), atom("000 "));
// check if there are leading bits that distinguish "abc" and " abc"
CAF_CHECK_NOT_EQUAL(atom("abc"), atom(" abc"));
// 'illegal' characters are mapped to whitespaces
CAF_CHECK_EQUAL(atom(" "), atom("@!?"));
// check to_string impl.
CAF_CHECK_EQUAL(to_string(s_foo), "FooBar");
CAF_TEST(receive_atoms) {
scoped_actor self;
send_to_self f{self.get()};
f(atom("foo"), static_cast<uint32_t>(42));
......@@ -83,21 +64,18 @@ int main() {
f(atom("a"), atom("b"), atom("c"), 23.f);
bool matched_pattern[3] = {false, false, false};
int i = 0;
CAF_CHECKPOINT();
CAF_TEST_VERBOSE("start receive loop");
for (i = 0; i < 3; ++i) {
self->receive(
on(atom("foo"), arg_match) >> [&](uint32_t value) {
CAF_CHECKPOINT();
matched_pattern[0] = true;
CAF_CHECK_EQUAL(value, 42);
},
on(atom(":Attach"), atom(":Baz"), arg_match) >> [&](const string& str) {
CAF_CHECKPOINT();
on(atom(":Attach"), atom(":Baz"), arg_match) >> [&](const std::string& str) {
matched_pattern[1] = true;
CAF_CHECK_EQUAL(str, "cstring");
},
on(atom("a"), atom("b"), atom("c"), arg_match) >> [&](float value) {
CAF_CHECKPOINT();
matched_pattern[2] = true;
CAF_CHECK_EQUAL(value, 23.f);
});
......@@ -105,8 +83,12 @@ int main() {
CAF_CHECK(matched_pattern[0] && matched_pattern[1] && matched_pattern[2]);
self->receive(
// "erase" message { atom("b"), atom("a"), atom("c"), 23.f }
others >> CAF_CHECKPOINT_CB(),
after(std::chrono::seconds(0)) >> CAF_UNEXPECTED_TOUT_CB()
others >> [] {
CAF_TEST_VERBOSE("drain mailbox");
},
after(std::chrono::seconds(0)) >> [] {
CAF_TEST_ERROR("mailbox empty");
}
);
atom_value x = atom("abc");
atom_value y = abc_atom::value;
......@@ -114,10 +96,33 @@ int main() {
auto msg = make_message(abc_atom());
self->send(self, msg);
self->receive(
on(atom("abc")) >> CAF_CHECKPOINT_CB(),
others >> CAF_UNEXPECTED_MSG_CB_REF(self)
on(atom("abc")) >> [] {
CAF_TEST_VERBOSE("received 'abc'");
},
others >> [] {
CAF_TEST_ERROR("unexpected message");
}
);
}
using testee = typed_actor<replies_to<abc_atom>::with<int>>;
testee::behavior_type testee_impl(testee::pointer self) {
return {
[=](abc_atom) {
CAF_TEST_VERBOSE("received abc_atom");
self->quit();
return 42;
}
};
}
CAF_TEST(sync_send_atom_constants) {
scoped_actor self;
auto tst = spawn_typed(testee_impl);
self->sync_send(tst, abc_atom::value).await(
[](int i) {
CAF_CHECK_EQUAL(i, 42);
}
);
test_typed_atom_interface();
shutdown();
return CAF_TEST_RESULT();
}
......@@ -17,19 +17,63 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE "example"
#define CAF_SUITE extract
#include "caf/test/unit_test.hpp"
CAF_TEST("foo")
{
CAF_CHECK(true);
CAF_CHECK(!false);
#include <string>
#include <vector>
#include "caf/all.hpp"
using namespace caf;
using std::string;
CAF_TEST(simple_ints) {
auto msg = make_message(1, 2, 3);
auto one = on(1) >> [] { };
auto two = on(2) >> [] { };
auto three = on(3) >> [] { };
auto skip_two = [](int i) -> optional<skip_message_t> {
if (i == 2) {
return skip_message();
}
return none;
};
CAF_CHECK_EQUAL(msg.extract(one), make_message(2, 3));
CAF_CHECK_EQUAL(msg.extract(two), make_message(1, 3));
CAF_CHECK_EQUAL(msg.extract(three), make_message(1, 2));
CAF_CHECK_EQUAL(msg.extract(skip_two), make_message(2));
}
CAF_TEST(type_sequences) {
auto _64 = uint64_t{64};
auto msg = make_message(1.0, 2.f, "str", 42, _64);
auto df = [](double, float) { };
auto fs = [](float, const string&) { };
auto iu = [](int, uint64_t) { };
CAF_CHECK_EQUAL(msg.extract(df), make_message("str", 42, _64));
CAF_CHECK_EQUAL(msg.extract(fs), make_message(1.0, 42, _64));
CAF_CHECK_EQUAL(msg.extract(iu), make_message(1.0, 2.f, "str"));
}
CAF_TEST("bar")
{
auto i = 42;
i *= 2;
CAF_REQUIRE(i == 84);
CAF_TEST(cli_args) {
std::vector<string> args{"-n", "-v", "5", "--out-file=/dev/null"};
int verbosity = 0;
string output_file;
string input_file;
auto res = message_builder(args.begin(), args.end()).extract_opts({
{"no-colors,n", "disable colors"},
{"out-file,o", "redirect output", output_file},
{"in-file,i", "read from file", input_file},
{"verbosity,v", "1-5", verbosity}
});
CAF_CHECK_EQUAL(res.remainder.size(), 0);
CAF_CHECK_EQUAL(to_string(res.remainder), to_string(message{}));
CAF_CHECK_EQUAL(res.opts.count("no-colors"), 1);
CAF_CHECK_EQUAL(res.opts.count("verbosity"), 1);
CAF_CHECK_EQUAL(res.opts.count("out-file"), 1);
CAF_CHECK_EQUAL(res.opts.count("in-file"), 0);
CAF_CHECK_EQUAL(output_file, "/dev/null");
CAF_CHECK_EQUAL(input_file, "");
}
cmake_minimum_required(VERSION 2.8)
project(caf_test C CXX)
file(GLOB LIBCAF_TEST_HDRS "caf/test/*.hpp")
file(GLOB_RECURSE LIBCAF_TEST_SRCS src/unit_test.cpp)
# build shared library if not compiling static only
if (NOT CAF_BUILD_STATIC_ONLY)
add_library(libcaf_test SHARED ${LIBCAF_TEST_SRCS} ${LIBCAF_TEST_HDRS})
target_link_libraries(libcaf_test ${LD_FLAGS})
set(LIBRARY_SOVERSION ${CAF_VERSION_MAJOR})
set_target_properties(libcaf_test
PROPERTIES
SOVERSION ${LIBRARY_SOVERSION}
VERSION ${CAF_VERSION}
OUTPUT_NAME caf_test)
if(NOT WIN32)
install(TARGETS libcaf_test LIBRARY DESTINATION lib)
endif()
endif ()
# build static library only if --build-static or --build-static-only was set
if (CAF_BUILD_STATIC_ONLY OR CAF_BUILD_STATIC)
add_library(libcaf_testStatic STATIC ${LIBCAF_TEST_HDRS} ${LIBCAF_TEST_SRCS})
set_target_properties(libcaf_testStatic
PROPERTIES OUTPUT_NAME caf_test_static)
if(NOT WIN32)
install(TARGETS libcaf_testStatic ARCHIVE DESTINATION lib)
endif()
endif ()
link_directories(${LD_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# install includes
if(NOT WIN32)
install(DIRECTORY caf/ DESTINATION include/caf FILES_MATCHING PATTERN "*.hpp")
endif()
# The single unit test binary containing all tests.
file(GLOB_RECURSE tests test.cpp test/*.cpp)
add_executable(caf-test ${tests})
target_link_libraries(caf-test
libcaf_test
${LD_FLAGS}
${LIBCAF_CORE_LIBRARY}
${PTHREAD_LIBRARIES})
add_custom_target(all_unit_tests)
add_dependencies(caf-test all_unit_tests)
# Enumerate all test suites.
foreach(test ${tests})
file(STRINGS ${test} contents)
foreach(line ${contents})
if ("${line}" MATCHES "CAF_SUITE(.*)")
string(REGEX REPLACE ".*\"(.*)\".*" "\\1" suite ${line})
list(APPEND suites ${suite})
endif()
endforeach()
endforeach()
list(REMOVE_DUPLICATES suites)
# Creates one CMake test per test suite.
macro (make_test suite)
string(REPLACE " " "_" test_name ${suite})
set(caf_test ${EXECUTABLE_OUTPUT_PATH}/caf-test)
add_test(${test_name} ${caf_test} -n -v 3 -s "${suite}" ${ARGN})
endmacro ()
foreach(suite ${suites})
make_test("${suite}")
endforeach ()
......@@ -21,13 +21,15 @@
#define CAF_TEST_UNIT_TEST_HPP
#include <map>
#include <regex>
#include <cmath>
#include <mutex>
#include <vector>
#include <string>
#include <memory>
#include <fstream>
#include <iostream>
#include <mutex>
#include <regex>
#include <sstream>
#include <iostream>
namespace caf {
namespace test {
......@@ -41,35 +43,43 @@ class test {
virtual ~test();
size_t __expected_failures() const;
size_t expected_failures() const;
void __pass(std::string msg);
void pass(std::string msg);
void __fail(std::string msg, bool expected);
void fail(std::string msg, bool expected);
const std::vector<std::pair<bool, std::string>>& __trace() const;
const std::string& name() const;
const std::string& __name() const;
inline size_t good() {
return m_good;
}
virtual void __run() = 0;
inline size_t bad() {
return m_bad;
}
virtual void run() = 0;
private:
size_t m_expected_failures = 0;
std::vector<std::pair<bool, std::string>> m_trace;
size_t m_expected_failures;
std::string m_name;
size_t m_good;
size_t m_bad;
};
namespace detail {
// Thrown when a required check fails.
struct require_error : std::logic_error {
require_error(const std::string& msg) : std::logic_error(msg) { }
// thrown when a required check fails
class require_error : std::logic_error {
public:
require_error(const std::string& msg);
require_error(const require_error&) = default;
require_error(require_error&&) = default;
~require_error() noexcept;
};
// Constructs spacing given a line number.
// constructs spacing given a line number.
const char* fill(size_t line);
} // namespace detail
......@@ -87,19 +97,37 @@ class logger {
massive = 4
};
class message {
/**
* Output stream for logging purposes.
*/
class stream {
public:
message(logger& l, level lvl);
stream(logger& l, level lvl);
stream(stream&&);
template <class T>
message& operator<<(const T& x) {
m_logger.log(m_level, x);
typename std::enable_if<
!std::is_same<T, char*>::value,
stream&
>::type
operator<<(const T& x) {
m_buf << x;
return *this;
}
stream& operator<<(const char& c);
stream& operator<<(const char* cstr);
stream& operator<<(const std::string& str);
private:
void flush();
logger& m_logger;
level m_level;
std::ostringstream m_buf;
};
static bool init(int lvl_cons, int lvl_file, const std::string& logfile);
......@@ -118,10 +146,10 @@ class logger {
}
}
message error();
message info();
message verbose();
message massive();
stream error();
stream info();
stream verbose();
stream massive();
private:
logger();
......@@ -155,8 +183,6 @@ enum color_value {
* Drives unit test execution.
*/
class engine {
engine() = default;
public:
/**
* Adds a test to the engine.
......@@ -182,10 +208,10 @@ class engine {
int verbosity_console,
int verbosity_file,
int max_runtime,
const std::regex& suites,
const std::regex& not_suites,
const std::regex& tests,
const std::regex& not_tests);
const std::string& suites,
const std::string& not_suites,
const std::string& tests,
const std::string& not_tests);
/**
* Retrieves a UNIX terminal color code or an empty string based on the
......@@ -201,7 +227,11 @@ class engine {
static test* current_test();
static std::vector<std::string> available_suites();
private:
engine() = default;
static engine& instance();
static std::string render(std::chrono::microseconds t);
......@@ -267,6 +297,36 @@ showable<T> show(T const &x) {
return showable<T>{x};
}
template <class T,
bool IsFloat = std::is_floating_point<T>::value,
bool IsIntegral = std::is_integral<T>::value>
class lhs_cmp {
public:
template <class U>
bool operator()(const T& x, const U& y) {
return x == y;
}
};
template <class T>
class lhs_cmp<T, true, false> {
public:
template <class U>
bool operator()(const T& x, const U& y) {
using rt = decltype(x - y);
return std::fabs(x - y) <= std::numeric_limits<rt>::epsilon();
}
};
template <class T>
class lhs_cmp<T, false, true> {
public:
template <class U>
bool operator()(const T& x, const U& y) {
return x == static_cast<T>(y);
}
};
template <class T>
struct lhs {
public:
......@@ -293,53 +353,74 @@ struct lhs {
template <class U>
using elevated =
typename std::conditional<std::is_convertible<U, T>::value, T, U>::type;
typename std::conditional<
std::is_convertible<U, T>::value,
T,
U
>::type;
explicit operator bool() {
m_evaluated = true;
return !! m_value ? pass() : fail_unary();
return static_cast<bool>(m_value) ? pass() : fail_unary();
}
// pass-or-fail
template <class U>
bool operator==(const U& u) {
bool pof(bool res, const U& x) {
m_evaluated = true;
return m_value == static_cast<elevated<U>>(u) ? pass() : fail(u);
return res ? pass() : fail(x);
}
template <class U>
bool operator!=(const U& u) {
m_evaluated = true;
return m_value != static_cast<elevated<U>>(u) ? pass() : fail(u);
bool operator==(const U& x) {
lhs_cmp<T> cmp;
return pof(cmp(m_value, x), x);
}
template <class U>
bool operator<(const U& u) {
m_evaluated = true;
return m_value < static_cast<elevated<U>>(u) ? pass() : fail(u);
bool operator!=(const U& x) {
lhs_cmp<T> cmp;
return pof(!cmp(m_value, x), x);
}
template <class U>
bool operator<=(const U& u) {
m_evaluated = true;
return m_value <= static_cast<elevated<U>>(u) ? pass() : fail(u);
bool operator<(const U& x) {
return pof(m_value < static_cast<elevated<U>>(x), x);
}
template <class U>
bool operator>(const U& u) {
m_evaluated = true;
return m_value > static_cast<elevated<U>>(u) ? pass() : fail(u);
bool operator<=(const U& x) {
return pof(m_value <= static_cast<elevated<U>>(x), x);
}
template <class U>
bool operator>=(const U& u) {
m_evaluated = true;
return m_value >= static_cast<elevated<U>>(u) ? pass() : fail(u);
bool operator>(const U& x) {
return pof(m_value > static_cast<elevated<U>>(x), x);
}
template <class U>
bool operator>=(const U& x) {
return pof(m_value >= static_cast<elevated<U>>(x), x);
}
private:
template<class V = T>
auto eval(int) -> decltype(! std::declval<V>()) {
return !! m_value;
typename std::enable_if<
std::is_convertible<V, bool>::value
&& !std::is_floating_point<V>::value,
bool
>::type
eval(int) {
return static_cast<bool>(m_value);
}
template<class V = T>
typename std::enable_if<
std::is_floating_point<V>::value,
bool
>::type
eval(int) {
return std::fabs(m_value) <= std::numeric_limits<V>::epsilon();
}
bool eval(long) {
......@@ -349,47 +430,44 @@ struct lhs {
bool pass() {
m_passed = true;
std::stringstream ss;
ss
<< engine::color(green) << "** "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr;
m_test->__pass(ss.str());
ss << engine::color(green) << "** "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr;
m_test->pass(ss.str());
return true;
}
bool fail_unary() {
std::stringstream ss;
ss
<< engine::color(red) << "!! "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr;
m_test->__fail(ss.str(), m_should_fail);
ss << engine::color(red) << "!! "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr;
m_test->fail(ss.str(), m_should_fail);
return false;
}
template <class U>
bool fail(const U& u) {
std::stringstream ss;
ss
<< engine::color(red) << "!! "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr << engine::color(magenta) << " ("
<< engine::color(red) << show(m_value) << engine::color(magenta) << " !! "
<< engine::color(red) << show(u) << engine::color(magenta) << ')'
<< engine::color(reset);
m_test->__fail(ss.str(), m_should_fail);
ss << engine::color(red) << "!! "
<< engine::color(blue) << m_filename << engine::color(yellow) << ":"
<< engine::color(blue) << m_line << fill(m_line) << engine::color(reset)
<< m_expr << engine::color(magenta) << " ("
<< engine::color(red) << show(m_value) << engine::color(magenta)
<< " !! " << engine::color(red) << show(u) << engine::color(magenta)
<< ')' << engine::color(reset);
m_test->fail(ss.str(), m_should_fail);
return false;
}
bool m_evaluated = false;
bool m_passed = false;
test* m_test;
const char *m_filename;
const char* m_filename;
size_t m_line;
const char *m_expr;
const char* m_expr;
bool m_should_fail;
const T& m_value;
};
......@@ -416,67 +494,78 @@ private:
} // namespace test
} // namespace caf
#define CAF_TEST_ERROR(msg) \
::caf::test::logger::instance().error() << msg << '\n'
#define CAF_TEST_INFO(msg) \
::caf::test::logger::instance().info() << msg << '\n'
#define CAF_TEST_VERBOSE(msg) \
::caf::test::logger::instance().verbose() << msg << '\n'
#define CAF_TEST_PR(level, msg) \
::caf::test::logger::instance(). level () \
<< ::caf::test::engine::color(::caf::test::yellow) \
<< " -> " << ::caf::test::engine::color(::caf::test::reset) << msg << '\n'
#define CAF_TEST_ERROR(msg) \
CAF_TEST_PR(error, msg)
#define CAF_TEST_INFO(msg) \
CAF_TEST_PR(info, msg)
#define CAF_TEST_VERBOSE(msg) \
CAF_TEST_PR(verbose, msg)
#define CAF_PASTE_CONCAT(lhs, rhs) lhs ## rhs
#define CAF_PASTE(lhs, rhs) CAF_PASTE_CONCAT(lhs, rhs)
#define CAF_UNIQUE(name) CAF_PASTE(name, __LINE__)
#ifndef CAF_SUITE
#define CAF_SUITE ""
#define CAF_SUITE unnamed
#endif
#define CAF_CHECK(...) \
do { \
(void)(::caf::test::detail::expr{ \
::caf::test::engine::current_test(), __FILE__, __LINE__, \
false, #__VA_ARGS__} ->* __VA_ARGS__); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while (false)
#define CAF_FAIL(...) \
do { \
(void)(::caf::test::detail::expr{ \
::caf::test::engine::current_test(), __FILE__, __LINE__, \
true, #__VA_ARGS__} ->* __VA_ARGS__); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while (false)
#define CAF_REQUIRE(...) \
do { \
auto CAF_UNIQUE(__result) = \
::caf::test::detail::expr{::caf::test::engine::current_test(), \
__FILE__, __LINE__, false, #__VA_ARGS__} ->* __VA_ARGS__; \
if (! CAF_UNIQUE(__result)) { \
throw ::caf::test::detail::require_error{#__VA_ARGS__}; \
} \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while (false)
#define CAF_TEST(name) \
namespace { \
struct CAF_UNIQUE(test) : public ::caf::test::test { \
CAF_UNIQUE(test)() : test{name} { } \
void __run() final; \
}; \
::caf::test::detail::adder<CAF_UNIQUE(test)> CAF_UNIQUE(a){CAF_SUITE}; \
} /* namespace <anonymous> */ \
void CAF_UNIQUE(test)::__run()
// Boost Test compatibility macros
#define CAF_STR(s) #s
#define CAF_XSTR(s) CAF_STR(s)
#define CAF_CHECK(...) \
{ \
static_cast<void>(::caf::test::detail::expr{ \
::caf::test::engine::current_test(), __FILE__, __LINE__, \
false, #__VA_ARGS__} ->* __VA_ARGS__); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} static_cast<void>(0)
#define CAF_FAIL(...) \
{ \
(void)(::caf::test::detail::expr{ \
::caf::test::engine::current_test(), __FILE__, __LINE__, \
true, #__VA_ARGS__} ->* __VA_ARGS__); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} static_cast<void>(0)
#define CAF_REQUIRE(...) \
{ \
auto CAF_UNIQUE(__result) = \
::caf::test::detail::expr{::caf::test::engine::current_test(), \
__FILE__, __LINE__, false, #__VA_ARGS__} ->* __VA_ARGS__; \
if (!CAF_UNIQUE(__result)) { \
throw ::caf::test::detail::require_error{#__VA_ARGS__}; \
} \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} static_cast<void>(0)
#define CAF_TEST(name) \
namespace { \
struct CAF_UNIQUE(test) : public ::caf::test::test { \
CAF_UNIQUE(test)() : test{CAF_XSTR(name)} { } \
void run() final; \
}; \
::caf::test::detail::adder<CAF_UNIQUE(test)> CAF_UNIQUE(a) { \
CAF_XSTR(CAF_SUITE) \
}; \
} /* namespace <anonymous> */ \
void CAF_UNIQUE(test)::run()
// Boost Test compatibility macro
#define CAF_CHECK_EQUAL(x, y) CAF_CHECK(x == y)
#define CAF_CHECK_NE(x, y) CAF_CHECK(x != y)
#define CAF_CHECK_LT(x, y) CAF_CHECK(x < y)
#define CAF_CHECK_LE(x, y) CAF_CHECK(x <= y)
#define CAF_CHECK_GT(x, y) CAF_CHECK(x > y)
#define CAF_CHECK_GE(x, y) CAF_CHECK(x >= y)
#endif // CAF_TEST_UNIT_TEST_HPP
......@@ -20,9 +20,17 @@
#ifndef CAF_TEST_UNIT_TEST_IMPL_HPP
#define CAF_TEST_UNIT_TEST_IMPL_HPP
#include <thread>
#include <cassert>
#include <cstdlib>
#include <algorithm>
#include <condition_variable>
#include <thread>
#include "caf/shutdown.hpp"
#include "caf/message_builder.hpp"
#include "caf/message_handler.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/await_all_actors_done.hpp"
namespace caf {
namespace test {
......@@ -74,7 +82,11 @@ void watchdog::stop() {
delete s_watchdog;
}
test::test(std::string name) : m_name{std::move(name)} {
test::test(std::string test_name)
: m_expected_failures(0),
m_name(std::move(test_name)),
m_good(0),
m_bad(0) {
// nop
}
......@@ -82,32 +94,33 @@ test::~test() {
// nop
}
size_t test::__expected_failures() const {
size_t test::expected_failures() const {
return m_expected_failures;
}
void test::__pass(std::string msg) {
m_trace.emplace_back(true, std::move(msg));
void test::pass(std::string msg) {
++m_good;
::caf::test::logger::instance().massive() << " " << msg << '\n';
}
void test::__fail(std::string msg, bool expected) {
m_trace.emplace_back(false, std::move(msg));
void test::fail(std::string msg, bool expected) {
++m_bad;
::caf::test::logger::instance().massive() << " " << msg << '\n';
if (expected) {
++m_expected_failures;
}
}
const std::vector<std::pair<bool, std::string>>& test::__trace() const {
return m_trace;
}
const std::string& test::__name() const {
const std::string& test::name() const {
return m_name;
}
namespace detail {
require_error::require_error(const std::string& msg) : std::logic_error(msg) {
// nop
}
require_error::~require_error() noexcept {
// nop
}
......@@ -126,18 +139,59 @@ const char* fill(size_t line) {
} // namespace detail
logger::message::message(logger& l, level lvl)
: m_logger(l),
m_level(lvl) {
logger::stream::stream(logger& l, level lvl) : m_logger(l), m_level(lvl) {
// nop
}
bool logger::init(int lvl_cons, int lvl_file,
const std::string& logfile) {
logger::stream::stream(stream&& other)
: m_logger(other.m_logger)
, m_level(other.m_level) {
// ostringstream does have swap since C++11... but not in GCC 4.7
m_buf.str(other.m_buf.str());
}
logger::stream& logger::stream::operator<<(const char& c) {
m_buf << c;
if (c == '\n') {
flush();
}
return *this;
}
logger::stream& logger::stream::operator<<(const char* cstr) {
if (*cstr == '\0') {
return *this;
}
m_buf << cstr;
if (cstr[strlen(cstr) - 1] == '\n') {
flush();
}
return *this;
}
logger::stream& logger::stream::operator<<(const std::string& str) {
if (str.empty()) {
return *this;
}
m_buf << str;
if (str.back() == '\n') {
flush();
}
return *this;
}
void logger::stream::flush() {
m_logger.log(m_level, m_buf.str());
m_buf.str("");
}
bool logger::init(int lvl_cons, int lvl_file, const std::string& logfile) {
instance().m_level_console = static_cast<level>(lvl_cons);
instance().m_level_file = static_cast<level>(lvl_file);
if (! logfile.empty()) {
if (!logfile.empty()) {
instance().m_file.open(logfile, std::ofstream::out | std::ofstream::app);
return !! instance().m_file;
return !!instance().m_file;
}
return true;
}
......@@ -147,45 +201,51 @@ logger& logger::instance() {
return l;
}
logger::message logger::error() {
return message{*this, level::error};
logger::stream logger::error() {
return stream{*this, level::error};
}
logger::message logger::info() {
return message{*this, level::info};
logger::stream logger::info() {
return stream{*this, level::info};
}
logger::message logger::verbose() {
return message{*this, level::verbose};
logger::stream logger::verbose() {
return stream{*this, level::verbose};
}
logger::message logger::massive() {
return message{*this, level::massive};
logger::stream logger::massive() {
return stream{*this, level::massive};
}
logger::logger() : m_console(std::cerr) {}
logger::logger() : m_console(std::cerr) {
// nop
}
void engine::add(const char* name, std::unique_ptr<test> t) {
auto& suite = instance().m_suites[std::string{name ? name : ""}];
for (auto& x : suite)
if (x->__name() == t->__name()) {
std::cout << "duplicate test name: " << t->__name() << '\n';
for (auto& x : suite) {
if (x->name() == t->name()) {
std::cout << "duplicate test name: " << t->name() << '\n';
std::abort();
}
}
suite.emplace_back(std::move(t));
}
bool engine::run(bool colorize,
const std::string& log_file,
int verbosity_console,
int verbosity_file,
int,
const std::regex& suites,
const std::regex& not_suites,
const std::regex& tests,
const std::regex& not_tests) {
if (! colorize) {
const std::string& log_file,
int verbosity_console,
int verbosity_file,
int, // TODO: max runtime
const std::string& suites_str,
const std::string& not_suites_str,
const std::string& tests_str,
const std::string& not_tests_str) {
if (not_suites_str == "*" || not_tests_str == "*") {
// nothing to do
return true;
}
if (!colorize) {
instance().m_reset = "";
instance().m_black = "";
instance().m_red = "";
......@@ -204,8 +264,9 @@ bool engine::run(bool colorize,
instance().m_bold_cyan = "";
instance().m_bold_white = "";
}
if (! logger::init(verbosity_console, verbosity_file, log_file))
if (!logger::init(verbosity_console, verbosity_file, log_file)) {
return false;
}
auto& log = logger::instance();
std::chrono::microseconds runtime{0};
size_t total_suites = 0;
......@@ -215,32 +276,76 @@ bool engine::run(bool colorize,
size_t total_bad_expected = 0;
auto bar = '+' + std::string(70, '-') + '+';
auto failed_require = false;
# if !defined(__clang__) && defined(__GNUC__) \
&& __GNUC__ == 4 && __GNUC_MINOR__ < 9
// regex implementation is broken prior to 4.9
using strvec = std::vector<std::string>;
auto from_psv = [](const std::string& psv) -> strvec {
// psv == pipe-separated-values
strvec result;
if (psv != ".*") {
split(result, psv, "|", token_compress_on);
std::sort(result.begin(), result.end());
}
return result;
};
auto suites = from_psv(suites_str);
auto not_suites = from_psv(not_suites_str);
auto tests = from_psv(tests_str);
auto not_tests = from_psv(not_tests_str);
auto enabled = [](const strvec& whitelist,
const strvec& blacklist,
const std::string& x) {
// an empty whitelist means original input was ".*", i.e., enable all
return !std::binary_search(blacklist.begin(), blacklist.end(), x)
&& (whitelist.empty()
|| std::binary_search(whitelist.begin(), whitelist.end(), x));
};
# else
std::regex suites{suites_str};
std::regex tests{tests_str};
std::regex not_suites;
std::regex not_tests;
// a default constructored regex matches is not equal to an "empty" regex
if (!not_suites_str.empty()) {
not_suites.assign(not_suites_str);
}
if (!not_tests_str.empty()) {
not_tests.assign(not_tests_str);
}
auto enabled = [](const std::regex& whitelist,
const std::regex& blacklist,
const std::string& x) {
// an empty whitelist means original input was "*", i.e., enable all
return std::regex_search(x, whitelist)
&& !std::regex_search(x, blacklist);
};
# endif
for (auto& p : instance().m_suites) {
if (! std::regex_search(p.first, suites) ||
std::regex_search(p.first, not_suites))
if (!enabled(suites, not_suites, p.first)) {
continue;
}
auto suite_name = p.first.empty() ? "<unnamed>" : p.first;
auto pad = std::string((bar.size() - suite_name.size()) / 2, ' ');
bool displayed_header = false;
size_t tests_ran = 0;
for (auto& t : p.second) {
if (! std::regex_search(t->__name(), tests)
|| std::regex_search(t->__name(), not_tests))
if (!enabled(tests, not_tests, t->name())) {
continue;
}
instance().m_current_test = t.get();
++tests_ran;
if (! displayed_header) {
log.verbose()
<< color(yellow) << bar << '\n' << pad << suite_name << '\n' << bar
<< color(reset) << "\n\n";
if (!displayed_header) {
log.verbose() << color(yellow) << bar << '\n' << pad << suite_name
<< '\n' << bar << color(reset) << "\n\n";
displayed_header = true;
}
log.verbose()
<< color(yellow) << "- " << color(reset) << t->__name() << '\n';
log.verbose() << color(yellow) << "- " << color(reset) << t->name()
<< '\n';
auto start = std::chrono::high_resolution_clock::now();
watchdog::start();
try {
t->__run();
t->run();
} catch (const detail::require_error&) {
failed_require = true;
}
......@@ -250,77 +355,69 @@ bool engine::run(bool colorize,
std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
runtime += elapsed;
++total_tests;
size_t good = 0;
size_t bad = 0;
for (auto& trace : t->__trace()) {
if (trace.first) {
++good;
log.massive() << " " << trace.second << '\n';
} else {
++bad;
log.error() << " " << trace.second << '\n';
}
}
size_t good = t->good();
size_t bad = t->bad();
if (failed_require) {
log.error()
<< color(red) << " REQUIRED" << color(reset) << '\n'
<< " " << color(blue) << last_check_file() << color(yellow)
<< ":" << color(cyan) << last_check_line() << color(reset)
<< detail::fill(last_check_line()) << "had last successful check"
<< '\n';
log.error() << color(red) << " REQUIRED" << color(reset) << '\n'
<< " " << color(blue) << last_check_file()
<< color(yellow) << ":" << color(cyan) << last_check_line()
<< color(reset) << detail::fill(last_check_line())
<< "had last successful check" << '\n';
}
total_good += good;
total_bad += bad;
total_bad_expected += t->__expected_failures();
log.verbose()
<< color(yellow) << " -> " << color(cyan) << good + bad
<< color(reset) << " check" << (good + bad > 1 ? "s " : " ")
<< "took " << color(cyan) << render(elapsed) << color(reset);
if (bad > 0)
log.verbose()
<< " (" << color(green) << good << color(reset) << '/'
<< color(red) << bad << color(reset) << ")" << '\n';
else
total_bad_expected += t->expected_failures();
log.verbose() << color(yellow) << " -> " << color(cyan) << good + bad
<< color(reset) << " check" << (good + bad > 1 ? "s " : " ")
<< "took " << color(cyan) << render(elapsed)
<< color(reset);
if (bad > 0) {
log.verbose() << " (" << color(green) << good << color(reset) << '/'
<< color(red) << bad << color(reset) << ")" << '\n';
} else {
log.verbose() << '\n';
if (failed_require)
}
if (failed_require) {
break;
}
}
// We only counts suites for which have executed one or more tests.
if (tests_ran > 0)
// only count suites which have executed one or more tests
if (tests_ran > 0) {
++total_suites;
if (displayed_header)
}
if (displayed_header) {
log.verbose() << '\n';
if (failed_require)
}
if (failed_require) {
break;
}
}
auto percent_good =
static_cast<unsigned>(double(100000 * total_good)
static_cast<unsigned>(double(100000 * total_good)
/ double(total_good + total_bad)) / 1000.0;
auto title = std::string{"summary"};
auto pad = std::string((bar.size() - title.size()) / 2, ' ');
auto indent = std::string(24, ' ');
log.info()
<< color(cyan) << bar << '\n' << pad << title << '\n' << bar
<< color(reset) << "\n\n"
<< indent << "suites: " << color(yellow) << total_suites << color(reset)
<< '\n' << indent << "tests: " << color(yellow) << total_tests
<< color(reset) << '\n' << indent << "checks: " << color(yellow)
<< total_good + total_bad << color(reset);
log.info() << color(cyan) << bar << '\n' << pad << title << '\n' << bar
<< color(reset) << "\n\n" << indent << "suites: " << color(yellow)
<< total_suites << color(reset) << '\n' << indent
<< "tests: " << color(yellow) << total_tests << color(reset)
<< '\n' << indent << "checks: " << color(yellow)
<< total_good + total_bad << color(reset);
if (total_bad > 0) {
log.info()
<< " (" << color(green) << total_good << color(reset) << '/'
<< color(red) << total_bad << color(reset) << ")";
if (total_bad_expected)
log.info() << " (" << color(green) << total_good << color(reset) << '/'
<< color(red) << total_bad << color(reset) << ")";
if (total_bad_expected) {
log.info()
<< ' ' << color(cyan) << total_bad_expected << color(reset)
<< " failures expected";
}
}
log.info()
<< '\n' << indent << "time: " << color(yellow) << render(runtime)
<< '\n' << color(reset) << indent << "success: "
<< (total_bad > 0 ? color(green) : color(yellow))
<< percent_good << "%" << color(reset) << "\n\n"
<< color(cyan) << bar << color(reset) << '\n';
log.info() << '\n' << indent << "time: " << color(yellow)
<< render(runtime) << '\n' << color(reset) << indent
<< "success: " << (total_bad > 0 ? color(green) : color(yellow))
<< percent_good << "%" << color(reset) << "\n\n" << color(cyan)
<< bar << color(reset) << '\n';
return total_bad == total_bad_expected;
}
......@@ -396,6 +493,14 @@ test* engine::current_test() {
return instance().m_current_test;
}
std::vector<std::string> engine::available_suites() {
std::vector<std::string> result;
for (auto& kvp : instance().m_suites) {
result.push_back(kvp.first);
}
return result;
}
engine& engine::instance() {
static engine e;
return e;
......@@ -404,7 +509,7 @@ engine& engine::instance() {
std::string engine::render(std::chrono::microseconds t) {
return t.count() > 1000000
? (std::to_string(t.count() / 1000000) + '.'
+ std::to_string((t.count() % 1000000) / 10000) + " s")
+ std::to_string((t.count() % 1000000) / 10000) + " s")
: t.count() > 1000
? (std::to_string(t.count() / 1000) + " ms")
: (std::to_string(t.count()) + " us");
......@@ -426,4 +531,55 @@ expr::expr(test* parent, const char* filename, size_t lineno,
} // namespace test
} // namespace caf
int main(int argc, char** argv) {
using namespace caf;
// default values.
int verbosity_console = 3;
int verbosity_file = 3;
int max_runtime = 10;
std::string log_file;
std::string suites = ".*";
std::string not_suites;
std::string tests = ".*";
std::string not_tests;
// our simple command line parser.
auto res = message_builder(argv + 1, argv + argc).extract_opts({
{"no-colors,n", "disable coloring"},
{"log-file,l", "set output file", log_file},
{"console-verbosity,v", "set verbosity level of console (1-5)",
verbosity_console},
{"file-verbosity,V", "set verbosity level of file output (1-5)",
verbosity_file},
{"max-runtime,r", "set maximum runtime in seconds", max_runtime},
{"suites,s",
"define what suites to run, either * or a comma-separated list", suites},
{"not-suites,S", "exclude suites", not_suites},
{"tests,t", "set tests", tests},
{"not-tests,T", "exclude tests", not_tests},
{"available-suites,a", "print available suites"}
});
if (res.opts.count("help") > 0) {
return 0;
}
if (res.opts.count("available-suites") > 0) {
std::cout << "available suites:" << std::endl;
for (auto& s : test::engine::available_suites()) {
std::cout << " - " << s << std::endl;
}
return 0;
}
if (!res.remainder.empty()) {
std::cerr << "*** invalid command line options" << std::endl
<< res.helptext << std::endl;
return 1;
}
auto colorize = res.opts.count("no-colors") == 0;
auto result = test::engine::run(colorize, log_file, verbosity_console,
verbosity_file, max_runtime, suites,
not_suites, tests, not_tests);
await_all_actors_done();
shutdown();
return result ? 0 : 1;
}
#endif // CAF_TEST_UNIT_TEST_IMPL_HPP
#include <cstdlib>
#include "caf/all.hpp"
#include "caf/test/unit_test.hpp"
int main(int argc, char* argv[]) {
using namespace caf;
// Default values.
auto colorize = true;
std::string log_file;
int verbosity_console = 3;
int verbosity_file = 3;
int max_runtime = 10;
std::regex suites{".*"};
std::regex not_suites;
std::regex tests{".*"};
std::regex not_tests;
// Our simple command line parser.
message_handler parser{
on("-n") >> [&] {
colorize = false;
},
on("-l", arg_match) >> [&](const std::string& file_name) {
log_file = file_name;
},
on("-v", arg_match) >> [&](const std::string& n) {
verbosity_console = static_cast<int>(std::strtol(n.c_str(), nullptr, 10));
},
on("-V", arg_match) >> [&](const std::string& n) {
verbosity_file = static_cast<int>(std::strtol(n.c_str(), nullptr, 10));
},
on("-r", arg_match) >> [&](const std::string& n) {
max_runtime = static_cast<int>(std::strtol(n.c_str(), nullptr, 10));
},
on("-s", arg_match) >> [&](const std::string& str) {
suites = str;
},
on("-S", arg_match) >> [&](const std::string& str) {
not_suites = str;
},
on("-t", arg_match) >> [&](const std::string& str) {
tests = str;
},
on("-T", arg_match) >> [&](const std::string& str) {
not_tests = str;
},
others() >> [&] {
std::cerr << "invalid command line argument" << std::endl;
std::exit(1);
}
};
auto is_opt_char = [](char c) {
switch (c) {
default:
return false;
case 'n':
case 'l':
case 'v':
case 'V':
case 'r':
case 's':
case 'S':
case 't':
case 'T':
return true;
};
};
for (int i = 1; i < argc; ++i) {
message_builder builder;
std::string arg{argv[i]};
if (arg.empty() || arg[0] != '-') {
std::cerr << "invalid option: " << arg << std::endl;
return 1;
}
builder.append(std::move(arg));
if (i + 1 < argc) {
auto next = argv[i + 1];
if (*next == '\0' || next[0] != '-' || ! is_opt_char(next[1])) {
builder.append(std::string{next});
++i;
}
}
builder.apply(parser);
}
auto result = caf::test::engine::run(colorize, log_file, verbosity_console,
verbosity_file, max_runtime, suites,
not_suites, tests, not_tests);
return result ? 0 : 1;
}
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#include <iostream>
#include "ping_pong.hpp"
#define CAF_SUITE "spawn"
#include "caf/all.hpp"
#include "caf/detail/logging.hpp"
#include "caf/test/unit_test.hpp"
using std::cout;
using std::endl;
using namespace caf;
using namespace caf;
namespace {
size_t s_pongs = 0;
behavior ping_behavior(local_actor* self, size_t num_pings) {
return {
on(atom("pong"), arg_match) >> [=](int value)->message {
if (!self->current_sender()) {
CAF_TEST_ERROR("current_sender() invalid!");
}
CAF_TEST_INFO("received {'pong', " << value << "}");
// cout << to_string(self->current_message()) << endl;
if (++s_pongs >= num_pings) {
CAF_TEST_INFO("reached maximum, send {'EXIT', user_defined} "
<< "to last sender and quit with normal reason");
self->send_exit(self->current_sender(),
exit_reason::user_shutdown);
self->quit();
}
return make_message(atom("ping"), value);
},
others() >> [=] {
self->quit(exit_reason::user_shutdown);
}
};
}
behavior pong_behavior(local_actor* self) {
return {
on(atom("ping"), arg_match) >> [](int value)->message {
return make_message(atom("pong"), value + 1);
},
others() >> [=] {
self->quit(exit_reason::user_shutdown);
}
};
}
} // namespace <anonymous>
size_t pongs() { return s_pongs; }
void ping(blocking_actor* self, size_t num_pings) {
s_pongs = 0;
self->receive_loop(ping_behavior(self, num_pings));
}
void event_based_ping(event_based_actor* self, size_t num_pings) {
s_pongs = 0;
self->become(ping_behavior(self, num_pings));
}
void pong(blocking_actor* self, actor ping_actor) {
self->send(ping_actor, atom("pong"), 0); // kickoff
self->receive_loop(pong_behavior(self));
}
void event_based_pong(event_based_actor* self, actor ping_actor) {
CAF_LOGF_TRACE("ping_actor = " << to_string(ping_actor));
CAF_REQUIRE(ping_actor != invalid_actor);
self->send(ping_actor, atom("pong"), 0); // kickoff
self->become(pong_behavior(self));
}
CAF_TEST("ping-pong") {
using namespace caf;
scoped_actor self;
self->trap_exit(true);
auto ping_actor = self->spawn<monitored+blocking_api>(ping, 10);
auto pong_actor = self->spawn<monitored+blocking_api>(pong, ping_actor);
self->link_to(pong_actor);
int i = 0;
int flags = 0;
self->delayed_send(self, std::chrono::seconds(1), atom("FooBar"));
// wait for DOWN and EXIT messages of pong
self->receive_for(i, 4) (
[&](const exit_msg& em) {
CAF_CHECK(em.source == pong_actor);
CAF_CHECK(em.reason == exit_reason::user_shutdown);
flags |= 0x01;
},
[&](const down_msg& dm) {
if (dm.source == pong_actor) {
flags |= 0x02;
CAF_CHECK(dm.reason == exit_reason::user_shutdown);
}
else if (dm.source == ping_actor) {
flags |= 0x04;
CAF_CHECK(dm.reason == exit_reason::normal);
}
},
[&](const atom_value& val) {
CAF_CHECK(val == atom("FooBar"));
flags |= 0x08;
},
others() >> [&]() {
// FIXME: abort test with error message.
//CAF_FAILURE("unexpected message: " << to_string(self->current_message()));
},
after(std::chrono::seconds(5)) >> [&]() {
// FIXME: abort test with error message.
//CAF_FAILURE("timeout in file " << __FILE__ << " in line " << __LINE__);
}
);
// wait for termination of all spawned actors
self->await_all_other_actors_done();
CAF_CHECK(flags == 0x0F);
CAF_CHECK(pongs() == 10);
}
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#ifndef PING_PONG_HPP
#define PING_PONG_HPP
//#include "caf/actor.hpp"
#include <cstddef>
#include "caf/fwd.hpp"
void ping(caf::blocking_actor*, size_t num_pings);
void event_based_ping(caf::event_based_actor*, size_t num_pings);
void pong(caf::blocking_actor*, caf::actor ping_actor);
// returns the number of messages ping received
size_t pongs();
#endif // PING_PONG_HPP
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