Commit 58c573fe authored by Matthias Vallentin's avatar Matthias Vallentin Committed by Marian Triebe

Add new unit testing framework.

parent c75fde04
...@@ -329,10 +329,9 @@ set(LIBCAF_LIBRARIES "${LIBCAF_CORE_LIBRARY}" ...@@ -329,10 +329,9 @@ set(LIBCAF_LIBRARIES "${LIBCAF_CORE_LIBRARY}"
# add unit tests if not being told otherwise # add unit tests if not being told otherwise
if(NOT CAF_NO_UNIT_TESTS) if(NOT CAF_NO_UNIT_TESTS)
enable_testing() enable_testing()
add_subdirectory(unit_testing) add_subdirectory(libcaf_test)
add_dependencies(all_unit_tests libcaf_core libcaf_io) add_dependencies(all_unit_tests libcaf_io)
if(NOT CAF_NO_OPENCL if(NOT CAF_NO_OPENCL AND EXISTS "${CMAKE_SOURCE_DIR}/libcaf_opencl/CMakeLists.txt")
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libcaf_opencl/CMakeLists.txt")
add_subdirectory(libcaf_opencl/unit_testing) add_subdirectory(libcaf_opencl/unit_testing)
endif() endif()
endif() endif()
......
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 ()
CAF Unit Testing Framework
==========================
The CAF unit testing framework offers a simple API to write unit tests.
Concepts
--------
- A **check** represents a single verification of boolean operation.
- A **test** contains one or more checks.
- A **suite** groups tests together.
Example
-------
```cpp
#include <caf/test/unit_test.h>
CAF_SUITE("core")
CAF_TEST("multiply")
{
CAF_REQUIRE(0 * 1 == 0);
CAF_CHECK(42 + 42 == 84);
}
CAF_TEST("divide")
{
CAF_FAIL(0 / 1 == 0);
CAF_CHECK(1 / 1 == 0); // fails
}
```
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 CAF_TEST_UNIT_TEST_HPP
#define CAF_TEST_UNIT_TEST_HPP
#include <map>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <mutex>
#include <regex>
#include <sstream>
namespace caf {
namespace test {
/**
* A sequence of *checks*.
*/
class test {
public:
test(std::string name);
virtual ~test();
size_t __expected_failures() const;
void __pass(std::string msg);
void __fail(std::string msg, bool expected);
const std::vector<std::pair<bool, std::string>>& __trace() const;
const std::string& __name() const;
virtual void __run() = 0;
private:
size_t m_expected_failures = 0;
std::vector<std::pair<bool, std::string>> m_trace;
std::string m_name;
};
namespace detail {
// Thrown when a required check fails.
struct require_error : std::logic_error {
require_error(const std::string& msg) : std::logic_error(msg) { }
require_error(const require_error&) = default;
require_error(require_error&&) = default;
~require_error() noexcept;
};
// Constructs spacing given a line number.
const char* fill(size_t line);
} // namespace detail
/**
* Logs messages for the test framework.
*/
class logger {
public:
enum class level : int {
quiet = 0,
error = 1,
info = 2,
verbose = 3,
massive = 4
};
class message {
public:
message(logger& l, level lvl);
template <class T>
message& operator<<(const T& x) {
m_logger.log(m_level, x);
return *this;
}
private:
logger& m_logger;
level m_level;
};
static bool init(int lvl_cons, int lvl_file, const std::string& logfile);
static logger& instance();
template <class T>
void log(level lvl, const T& x) {
if (lvl <= m_level_console) {
std::lock_guard<std::mutex> io_guard{m_console_mtx};
m_console << x;
}
if (lvl <= m_level_file) {
std::lock_guard<std::mutex> io_guard{m_file_mtx};
m_file << x;
}
}
message error();
message info();
message verbose();
message massive();
private:
logger();
level m_level_console;
level m_level_file;
std::ostream& m_console;
std::ofstream m_file;
std::mutex m_console_mtx;
std::mutex m_file_mtx;
};
enum color_face {
normal,
bold
};
enum color_value {
reset,
black,
red,
green,
yellow,
blue,
magenta,
cyan,
white
};
/**
* Drives unit test execution.
*/
class engine {
engine() = default;
public:
/**
* Adds a test to the engine.
* @param name The name of the suite.
* @param t The test to register.
*/
static void add(const char* name, std::unique_ptr<test> t);
/**
* Invokes tests in all suites.
* @param colorize Whether to colorize the output.
* @param log_file The filename of the log output. The empty string means
* that no log file will be written.
* @param verbosity_console The log verbosity on the console.
* @param verbosity_file The log verbosity in the log file.
* @param max_runtime The maximum number of seconds a test shall run.
* @param suites The regular expression of the tests to run.
* @param not_suites Whether to colorize the output.
* @returns `true` iff all tests succeeded.
*/
static bool run(bool colorize,
const std::string& log_file,
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);
/**
* Retrieves a UNIX terminal color code or an empty string based on the
* color configuration of the engine.
*/
static const char* color(color_value v, color_face t = normal);
static const char* last_check_file();
static void last_check_file(const char* file);
static size_t last_check_line();
static void last_check_line(size_t line);
static test* current_test();
private:
static engine& instance();
static std::string render(std::chrono::microseconds t);
const char* m_reset = "\033[0m";
const char* m_black = "\033[30m";
const char* m_red = "\033[31m";
const char* m_green = "\033[32m";
const char* m_yellow = "\033[33m";
const char* m_blue = "\033[34m";
const char* m_magenta = "\033[35m";
const char* m_cyan = "\033[36m";
const char* m_white = "\033[37m";
const char* m_bold_black = "\033[1m\033[30m";
const char* m_bold_red = "\033[1m\033[31m";
const char* m_bold_green = "\033[1m\033[32m";
const char* m_bold_yellow = "\033[1m\033[33m";
const char* m_bold_blue = "\033[1m\033[34m";
const char* m_bold_magenta = "\033[1m\033[35m";
const char* m_bold_cyan = "\033[1m\033[36m";
const char* m_bold_white = "\033[1m\033[37m";
const char* m_check_file = "<none>";
size_t m_check_line = 0;
test* m_current_test = nullptr;
std::map<std::string, std::vector<std::unique_ptr<test>>> m_suites;
};
namespace detail {
template <class T>
struct adder {
adder(char const* name) {
engine::add(name, std::unique_ptr<T>{new T});
}
};
template <class T>
struct showable_base {};
template <class T>
std::ostream& operator<<(std::ostream& out, const showable_base<T>&) {
out << engine::color(blue) << "<unprintable>" << engine::color(reset);
return out;
}
template <class T>
class showable : public showable_base<T> {
public:
explicit showable(const T& x) : m_value(x) { }
template <class U = T>
friend auto operator<<(std::ostream& out, const showable& p)
-> decltype(out << std::declval<const U&>()) {
return out << p.m_value;
}
private:
const T& m_value;
};
template <class T>
showable<T> show(T const &x) {
return showable<T>{x};
}
template <class T>
struct lhs {
public:
lhs(test* parent, const char *file, size_t line, const char *expr,
bool should_fail, const T& x)
: m_test(parent),
m_filename(file),
m_line(line),
m_expr(expr),
m_should_fail(should_fail),
m_value(x) {
}
~lhs() {
if (m_evaluated) {
return;
}
if (eval(0)) {
pass();
} else {
fail_unary();
}
}
template <class U>
using elevated =
typename std::conditional<std::is_convertible<U, T>::value, T, U>::type;
explicit operator bool() {
m_evaluated = true;
return !! m_value ? pass() : fail_unary();
}
template <class U>
bool operator==(const U& u) {
m_evaluated = true;
return m_value == static_cast<elevated<U>>(u) ? pass() : fail(u);
}
template <class U>
bool operator!=(const U& u) {
m_evaluated = true;
return m_value != static_cast<elevated<U>>(u) ? pass() : fail(u);
}
template <class U>
bool operator<(const U& u) {
m_evaluated = true;
return m_value < static_cast<elevated<U>>(u) ? pass() : fail(u);
}
template <class U>
bool operator<=(const U& u) {
m_evaluated = true;
return m_value <= static_cast<elevated<U>>(u) ? pass() : fail(u);
}
template <class U>
bool operator>(const U& u) {
m_evaluated = true;
return m_value > static_cast<elevated<U>>(u) ? pass() : fail(u);
}
template <class U>
bool operator>=(const U& u) {
m_evaluated = true;
return m_value >= static_cast<elevated<U>>(u) ? pass() : fail(u);
}
private:
template<class V = T>
auto eval(int) -> decltype(! std::declval<V>()) {
return !! m_value;
}
bool eval(long) {
return true;
}
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());
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);
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);
return false;
}
bool m_evaluated = false;
bool m_passed = false;
test* m_test;
const char *m_filename;
size_t m_line;
const char *m_expr;
bool m_should_fail;
const T& m_value;
};
struct expr {
public:
expr(test* parent, const char* filename, size_t lineno, bool should_fail,
const char* expression);
template <class T>
lhs<T> operator->*(const T& x) {
return {m_test, m_filename, m_line, m_expr, m_should_fail, x};
}
private:
test* m_test;
const char* m_filename;
size_t m_line;
bool m_should_fail;
const char* m_expr;
};
} // namespace detail
} // 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_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 ""
#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_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
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 CAF_TEST_UNIT_TEST_IMPL_HPP
#define CAF_TEST_UNIT_TEST_IMPL_HPP
#include <cassert>
#include <condition_variable>
#include <thread>
namespace caf {
namespace test {
class watchdog {
public:
static void start();
static void stop();
private:
watchdog() {
m_thread = std::thread([&] {
auto tp =
std::chrono::high_resolution_clock::now() + std::chrono::seconds(10);
std::unique_lock<std::mutex> guard{m_mtx};
while (!m_canceled
&& m_cv.wait_until(guard, tp) != std::cv_status::timeout) {
// spin
}
if (!m_canceled) {
logger::instance().error()
<< "WATCHDOG: unit test did finish within 10s, abort\n";
abort();
}
});
}
~watchdog() {
{ // lifetime scope of guard
std::lock_guard<std::mutex> guard{m_mtx};
m_canceled = true;
m_cv.notify_all();
}
m_thread.join();
}
volatile bool m_canceled = false;
std::mutex m_mtx;
std::condition_variable m_cv;
std::thread m_thread;
};
namespace { watchdog* s_watchdog; }
void watchdog::start() {
s_watchdog = new watchdog();
}
void watchdog::stop() {
delete s_watchdog;
}
test::test(std::string name) : m_name{std::move(name)} {
// nop
}
test::~test() {
// nop
}
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::__fail(std::string msg, bool expected) {
m_trace.emplace_back(false, std::move(msg));
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 {
return m_name;
}
namespace detail {
require_error::~require_error() noexcept {
// nop
}
const char* fill(size_t line) {
if (line < 10) {
return " ";
} else if (line < 100) {
return " ";
} else if (line < 1000) {
return " ";
} else {
return " ";
}
}
} // namespace detail
logger::message::message(logger& l, level lvl)
: m_logger(l),
m_level(lvl) {
}
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()) {
instance().m_file.open(logfile, std::ofstream::out | std::ofstream::app);
return !! instance().m_file;
}
return true;
}
logger& logger::instance() {
static logger l;
return l;
}
logger::message logger::error() {
return message{*this, level::error};
}
logger::message logger::info() {
return message{*this, level::info};
}
logger::message logger::verbose() {
return message{*this, level::verbose};
}
logger::message logger::massive() {
return message{*this, level::massive};
}
logger::logger() : m_console(std::cerr) {}
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';
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) {
instance().m_reset = "";
instance().m_black = "";
instance().m_red = "";
instance().m_green = "";
instance().m_yellow = "";
instance().m_blue = "";
instance().m_magenta = "";
instance().m_cyan = "";
instance().m_white = "";
instance().m_bold_black = "";
instance().m_bold_red = "";
instance().m_bold_green = "";
instance().m_bold_yellow = "";
instance().m_bold_blue = "";
instance().m_bold_magenta = "";
instance().m_bold_cyan = "";
instance().m_bold_white = "";
}
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;
size_t total_tests = 0;
size_t total_good = 0;
size_t total_bad = 0;
size_t total_bad_expected = 0;
auto bar = '+' + std::string(70, '-') + '+';
auto failed_require = false;
for (auto& p : instance().m_suites) {
if (! std::regex_search(p.first, suites) ||
std::regex_search(p.first, not_suites))
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))
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";
displayed_header = true;
}
log.verbose()
<< color(yellow) << "- " << color(reset) << t->__name() << '\n';
auto start = std::chrono::high_resolution_clock::now();
watchdog::start();
try {
t->__run();
} catch (const detail::require_error&) {
failed_require = true;
}
watchdog::stop();
auto stop = std::chrono::high_resolution_clock::now();
auto elapsed =
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';
}
}
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';
}
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
log.verbose() << '\n';
if (failed_require)
break;
}
// We only counts suites for which have executed one or more tests.
if (tests_ran > 0)
++total_suites;
if (displayed_header)
log.verbose() << '\n';
if (failed_require)
break;
}
auto percent_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);
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(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';
return total_bad == total_bad_expected;
}
const char* engine::color(color_value v, color_face t) {
if (v == reset) {
return instance().m_reset;
}
switch (t) {
default:
return nullptr;
case normal:
switch (v) {
default:
return nullptr;
case black:
return instance().m_black;
case red:
return instance().m_red;
case green:
return instance().m_green;
case yellow:
return instance().m_yellow;
case blue:
return instance().m_blue;
case magenta:
return instance().m_magenta;
case cyan:
return instance().m_cyan;
case white:
return instance().m_white;
}
case bold:
switch (v) {
default:
return nullptr;
case black:
return instance().m_bold_black;
case red:
return instance().m_bold_red;
case green:
return instance().m_bold_green;
case yellow:
return instance().m_bold_yellow;
case blue:
return instance().m_bold_blue;
case magenta:
return instance().m_bold_magenta;
case cyan:
return instance().m_bold_cyan;
case white:
return instance().m_bold_white;
}
}
}
const char* engine::last_check_file() {
return instance().m_check_file;
}
void engine::last_check_file(const char* file) {
instance().m_check_file = file;
}
size_t engine::last_check_line() {
return instance().m_check_line;
}
void engine::last_check_line(size_t line) {
instance().m_check_line = line;
}
test* engine::current_test() {
return instance().m_current_test;
}
engine& engine::instance() {
static engine e;
return e;
}
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")
: t.count() > 1000
? (std::to_string(t.count() / 1000) + " ms")
: (std::to_string(t.count()) + " us");
}
namespace detail {
expr::expr(test* parent, const char* filename, size_t lineno,
bool should_fail, const char* expression)
: m_test{parent},
m_filename{filename},
m_line{lineno},
m_should_fail{should_fail},
m_expr{expression} {
assert(m_test != nullptr);
}
} // namespace detail
} // namespace test
} // namespace caf
#endif // CAF_TEST_UNIT_TEST_IMPL_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/test/unit_test.hpp"
#include "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. *
******************************************************************************/
#define CAF_SUITE "example"
#include "caf/test/unit_test.hpp"
CAF_TEST("foo")
{
CAF_CHECK(true);
CAF_CHECK(!false);
}
CAF_TEST("bar")
{
auto i = 42;
i *= 2;
CAF_REQUIRE(i == 84);
}
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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
#include <iostream>
#include "test.hpp"
#include "ping_pong.hpp"
#include "caf/all.hpp"
#include "caf/detail/logging.hpp"
using std::cout;
using std::endl;
using namespace caf;
namespace {
size_t s_pongs = 0;
behavior ping_behavior(local_actor* self, size_t num_pings) {
return {
[=](pong_atom, int value) -> message {
if (!self->current_sender()) {
CAF_PRINT("last_sender() invalid!");
}
CAF_PRINT("received {'pong', " << value << "}");
// cout << to_string(self->current_message()) << endl;
if (++s_pongs >= num_pings) {
CAF_PRINT("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(ping_atom::value, value);
},
others >> [=] {
CAF_PRINTERR("unexpected; " << to_string(self->current_message()));
self->quit(exit_reason::user_shutdown);
}
};
}
behavior pong_behavior(local_actor* self) {
return {
[](ping_atom, int value) -> message {
CAF_PRINT("received {'ping', " << value << "}");
return make_message(pong_atom::value, value + 1);
},
others >> [=] {
CAF_PRINTERR("unexpected; " << to_string(self->current_sender()));
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, pong_atom::value, 0); // kickoff
self->receive_loop(pong_behavior(self));
}
#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
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include <functional> #include <functional>
#include "test.hpp" #include "test.hpp"
#include "ping_pong.hpp"
#include "caf/all.hpp" #include "caf/all.hpp"
...@@ -713,7 +712,7 @@ void test_spawn() { ...@@ -713,7 +712,7 @@ void test_spawn() {
self->await_all_other_actors_done(); self->await_all_other_actors_done();
// test sending message to self via scoped_actor // test sending message to self via scoped_actor
self->send(self, atom("check")); self->send(self, atom("check"));
self->receive ( self->receive(
on(atom("check")) >> [] { on(atom("check")) >> [] {
CAF_CHECKPOINT(); CAF_CHECKPOINT();
} }
......
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