Commit 7a77a40e authored by Dominik Charousset's avatar Dominik Charousset

Add initial version for the new testing framework

parent ff9b5667
...@@ -30,12 +30,13 @@ IndentCaseLabels: true ...@@ -30,12 +30,13 @@ IndentCaseLabels: true
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
IndentWidth: 2 IndentWidth: 2
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: true
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"
MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 1
NamespaceIndentation: None NamespaceIndentation: None
NamespaceMacros: ["SUITE", "WITH_FIXTURE"]
PenaltyBreakAssignment: 25 PenaltyBreakAssignment: 25
PenaltyBreakBeforeFirstCallParameter: 50 PenaltyBreakBeforeFirstCallParameter: 50
PenaltyReturnTypeOnItsOwnLine: 25 PenaltyReturnTypeOnItsOwnLine: 25
......
...@@ -207,25 +207,9 @@ else() ...@@ -207,25 +207,9 @@ else()
CACHE INTERNAL "The version string used for shared library objects") CACHE INTERNAL "The version string used for shared library objects")
endif() endif()
# -- install testing DSL headers ----------------------------------------------- # -- create the libcaf_test target ahead of time for caf_core ------------------
add_library(libcaf_test INTERFACE) add_library(libcaf_test)
set_target_properties(libcaf_test PROPERTIES EXPORT_NAME test)
target_include_directories(libcaf_test INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/libcaf_test>)
add_library(CAF::test ALIAS libcaf_test)
install(DIRECTORY libcaf_test/caf/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/caf
FILES_MATCHING PATTERN "*.hpp")
install(TARGETS libcaf_test
EXPORT CAFTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT test
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT test
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT test)
# -- add uninstall target if it does not exist yet ----------------------------- # -- add uninstall target if it does not exist yet -----------------------------
...@@ -356,16 +340,24 @@ function(caf_add_component name) ...@@ -356,16 +340,24 @@ function(caf_add_component name)
set_property(TARGET ${obj_lib_target} PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET ${obj_lib_target} PROPERTY POSITION_INDEPENDENT_CODE ON)
caf_target_link_libraries(${obj_lib_target} caf_target_link_libraries(${obj_lib_target}
${CAF_ADD_COMPONENT_DEPENDENCIES}) ${CAF_ADD_COMPONENT_DEPENDENCIES})
add_library(${pub_lib_target} if(NOT TARGET ${pub_lib_target})
"${PROJECT_SOURCE_DIR}/cmake/dummy.cpp" add_library(${pub_lib_target}
$<TARGET_OBJECTS:${obj_lib_target}>) "${PROJECT_SOURCE_DIR}/cmake/dummy.cpp"
$<TARGET_OBJECTS:${obj_lib_target}>)
else()
target_sources(
${pub_lib_target}
PRIVATE
"${PROJECT_SOURCE_DIR}/cmake/dummy.cpp"
$<TARGET_OBJECTS:${obj_lib_target}>)
endif()
if(CAF_ENABLE_TESTING AND CAF_ADD_COMPONENT_TEST_SOURCES) if(CAF_ENABLE_TESTING AND CAF_ADD_COMPONENT_TEST_SOURCES)
set(tst_bin_target "caf-${name}-test") set(tst_bin_target "caf-${name}-test")
list(APPEND targets ${tst_bin_target}) list(APPEND targets ${tst_bin_target})
add_executable(${tst_bin_target} add_executable(${tst_bin_target}
${CAF_ADD_COMPONENT_TEST_SOURCES} ${CAF_ADD_COMPONENT_TEST_SOURCES}
$<TARGET_OBJECTS:${obj_lib_target}>) $<TARGET_OBJECTS:${obj_lib_target}>)
target_link_libraries(${tst_bin_target} PRIVATE CAF::test target_link_libraries(${tst_bin_target} PRIVATE libcaf_test
${CAF_ADD_COMPONENT_DEPENDENCIES}) ${CAF_ADD_COMPONENT_DEPENDENCIES})
target_include_directories(${tst_bin_target} PRIVATE target_include_directories(${tst_bin_target} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/test") "${CMAKE_CURRENT_SOURCE_DIR}/test")
...@@ -401,6 +393,8 @@ endfunction() ...@@ -401,6 +393,8 @@ endfunction()
add_subdirectory(libcaf_core) add_subdirectory(libcaf_core)
add_subdirectory(libcaf_test)
if(CAF_ENABLE_NET_MODULE) if(CAF_ENABLE_NET_MODULE)
add_subdirectory(libcaf_net) add_subdirectory(libcaf_net)
endif() endif()
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
// `inspect` overload. // `inspect` overload.
#include "caf/detail/build_config.hpp" #include "caf/detail/build_config.hpp"
#include "caf/detail/source_location.hpp"
#include <string> #include <string>
...@@ -113,3 +114,32 @@ std::string format(std::string_view fstr, Args&&... args) { ...@@ -113,3 +114,32 @@ std::string format(std::string_view fstr, Args&&... args) {
} // namespace caf::detail } // namespace caf::detail
#endif #endif
namespace caf::detail {
/// Wraps a format string and its source location. Useful for logging functions
/// that have a variadic list of arguments and thus cannot use the usual way of
/// passing in a source location via default argument.
struct format_string_with_location {
constexpr format_string_with_location(std::string_view str,
const source_location& loc
= source_location::current())
: value(str), location(loc) {
// nop
}
constexpr format_string_with_location(const char* str,
const source_location& loc
= source_location::current())
: value(str), location(loc) {
// nop
}
/// The format string.
std::string_view value;
/// The source location of the format string.
source_location location;
};
} // namespace caf::detail
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <cstdint>
namespace caf::detail {
// Backport of C++20's source_location. Remove when upgrading to C+20.
class source_location {
public:
constexpr uint_least32_t line() const noexcept {
return line_;
}
constexpr uint_least32_t column() const noexcept {
return 0;
}
constexpr const char* file_name() const noexcept {
return file_;
}
constexpr const char* function_name() const noexcept {
return fn_;
}
static constexpr source_location
current(const char* file_name = __builtin_FILE(),
const char* fn_name = __builtin_FUNCTION(),
int line = __builtin_LINE()) noexcept {
source_location loc;
loc.file_ = file_name;
loc.fn_ = fn_name;
loc.line_ = line;
return loc;
}
private:
const char* file_ = "invalid";
const char* fn_ = "invalid";
uint_least32_t line_ = 0;
};
} // namespace caf::detail
# -- collect header files ------------------------------------------------------
file(GLOB_RECURSE CAF_TEST_HEADERS "caf/*.hpp")
# -- add targets ---------------------------------------------------------------
caf_add_component(
test
DEPENDENCIES
PUBLIC
CAF::core
PRIVATE
CAF::internal
ENUM_TYPES
test.block_type
HEADERS
${CAF_TEST_HEADERS}
SOURCES
src/test/and_given.cpp
src/test/and_then.cpp
src/test/and_when.cpp
src/test/block.cpp
src/test/context.cpp
src/test/factory.cpp
src/test/given.cpp
src/test/nesting_error.cpp
src/test/registry.cpp
src/test/reporter.cpp
src/test/runnable.cpp
src/test/runner.cpp
src/test/scenario.cpp
src/test/scope.cpp
src/test/section.cpp
src/test/test.cpp
src/test/then.cpp
src/test/when.cpp
TEST_SOURCES
test/test-test.cpp
TEST_SUITES
test.block_type
test.scenario
test.test)
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
namespace caf::test {
/// Represents an `AND_GIVEN` block.
class and_given : public block {
public:
using block::block;
block_type type() const noexcept override;
when* get_when(int id, std::string_view description,
const detail::source_location& loc) override;
and_when* get_and_when(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
namespace caf::test {
/// Represents an `AND_THEN` block.
class and_then : public block {
public:
using block::block;
block_type type() const noexcept override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
namespace caf::test {
/// Represents an `AND_WHEN` block.
class and_when : public block {
public:
using block::block;
block_type type() const noexcept override;
then* get_then(int id, std::string_view description,
const detail::source_location& loc) override;
and_then* get_and_then(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <string_view>
namespace caf::test {
/// Describes a binary predicate.
enum class binary_predicate {
/// Equal.
eq,
/// Not equal.
ne,
/// Less than.
lt,
/// Less than or equal.
le,
/// Greater than.
gt,
/// Greater than or equal.
ge,
};
/// Returns a string representation of `predicate` in C++ syntax.
constexpr std::string_view str(binary_predicate predicate) noexcept {
switch (predicate) {
default:
return "???";
case binary_predicate::eq:
return "==";
case binary_predicate::ne:
return "!=";
case binary_predicate::lt:
return "<";
case binary_predicate::le:
return "<=";
case binary_predicate::gt:
return ">";
case binary_predicate::ge:
return ">=";
}
}
/// Returns the negated predicate.
constexpr binary_predicate negate(binary_predicate predicate) noexcept {
switch (predicate) {
default:
return predicate;
case binary_predicate::eq:
return binary_predicate::ne;
case binary_predicate::ne:
return binary_predicate::eq;
case binary_predicate::lt:
return binary_predicate::ge;
case binary_predicate::le:
return binary_predicate::gt;
case binary_predicate::gt:
return binary_predicate::le;
case binary_predicate::ge:
return binary_predicate::lt;
}
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/test/fwd.hpp"
#include <string_view>
#include <vector>
namespace caf::test {
/// Represents a block of test logic. Blocks can be nested to form a tree-like
/// structure.
class CAF_TEST_EXPORT block {
public:
block(context_ptr ctx, int id, std::string_view description,
const detail::source_location& loc);
virtual ~block();
/// Returns the type of this block.
virtual block_type type() const noexcept = 0;
/// Returns the user-defined description of this block.
std::string_view description() const noexcept {
return description_;
}
/// Returns the source location of this block.
const detail::source_location& location() const noexcept {
return loc_;
}
/// Called at scope entry.
void enter();
/// Called from the root block to clean up a branch of the test.
void leave();
/// Customization point for performing sanity checks before leaving the block.
virtual void on_leave();
/// Checks whether this block can run. This is used to skip blocks that were
/// executed in a previous run or are scheduled to run in a future run.
bool can_run() const noexcept;
/// Checks whether this block is active. A block is active if it is currently
/// executed.
bool active() const noexcept {
return active_;
}
template <class T>
T* get_nested(int id, std::string_view description,
const detail::source_location& loc) {
auto& result = get_nested_or_construct(id);
if (!result) {
result = std::make_unique<T>(ctx_, id, description, loc);
nested_.push_back(result.get());
}
return static_cast<T*>(result.get());
}
virtual section* get_section(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual given* get_given(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual and_given* get_and_given(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual when* get_when(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual and_when* get_and_when(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual then* get_then(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual and_then* get_and_then(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
virtual but* get_but(int id, std::string_view description,
const detail::source_location& loc
= detail::source_location::current());
protected:
std::unique_ptr<block>& get_nested_or_construct(int id);
context_ptr ctx_;
int id_ = 0;
std::string_view description_;
bool active_ = false;
bool executed_ = false;
std::vector<block*> nested_;
detail::source_location loc_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/test_export.hpp"
#include <string>
#include <string_view>
#include <type_traits>
namespace caf::test {
/// Identifies a code block in a test definition.
enum class block_type {
/// Identifies a TEST block.
test,
/// Identifies a SECTION block in a TEST.
section,
/// Identifies a BDD-style SCENARIO block.
scenario,
/// Identifies a BDD-style GIVEN block.
given,
/// Identifies a BDD-style AND_GIVEN block.
and_given,
/// Identifies a BDD-style WHEN block.
when,
/// Identifies a BDD-style AND_WHEN block.
and_when,
/// Identifies a BDD-style THEN block.
then,
/// Identifies a BDD-style AND_THEN block.
and_then,
/// Identifies a BDD-style BUT block.
but,
};
/// Checks whether `type` is an extension type, i.e., AND_GIVEN, AND_WHEN,
/// AND_THEN, or BUT.
/// @relates block_type
constexpr bool is_extension(block_type type) noexcept {
switch (type) {
default:
return false;
case block_type::and_given:
case block_type::and_when:
case block_type::and_then:
case block_type::but:
return true;
}
}
/// @relates block_type
constexpr std::string_view macro_name(block_type type) noexcept {
switch (type) {
case block_type::test:
return "TEST";
case block_type::section:
return "SECTION";
case block_type::scenario:
return "SCENARIO";
case block_type::given:
return "GIVEN";
case block_type::and_given:
return "AND_GIVEN";
case block_type::when:
return "WHEN";
case block_type::and_when:
return "AND_WHEN";
case block_type::then:
return "THEN";
case block_type::and_then:
return "AND_THEN";
case block_type::but:
return "BUT";
}
return "???";
}
/// @relates block_type
constexpr std::string_view as_prefix(block_type type) noexcept {
switch (type) {
case block_type::test:
return "Test";
case block_type::section:
return "Section";
case block_type::scenario:
return "Scenario";
case block_type::given:
return "Given";
case block_type::when:
return "When";
case block_type::then:
return "Then";
case block_type::and_given:
case block_type::and_when:
case block_type::and_then:
return "And";
case block_type::but:
return "But";
}
return "???";
}
/// @relates block_type
CAF_TEST_EXPORT std::string to_string(block_type);
/// @relates block_type
CAF_TEST_EXPORT bool from_string(std::string_view, block_type&);
/// @relates block_type
CAF_TEST_EXPORT bool from_integer(std::underlying_type_t<block_type>,
block_type&);
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/exec_main.hpp"
#include "caf/test/runner.hpp"
#define CAF_TEST_MAIN(...) \
int main(int argc, char** argv) { \
[[maybe_unused]] auto host_init_guard = caf::detail::do_init_host_system( \
caf::detail::type_list<>{}, caf::detail::type_list<__VA_ARGS__>{}); \
caf::exec_main_init_meta_objects<__VA_ARGS__>(); \
caf::core::init_global_meta_objects(); \
caf::test::runner runner; \
return runner.run(argc, argv); \
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/test/block.hpp"
#include "caf/test/fwd.hpp"
#include <map>
#include <memory>
#include <string_view>
#include <vector>
namespace caf::test {
/// Represents the exeuction context of a test. The context stores all steps of
/// 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.
class CAF_TEST_EXPORT context : public std::enable_shared_from_this<context> {
public:
/// Returns whether the test is still active. A test is active as long as
/// no unwinding is in progress.
bool active() const noexcept {
return unwind_stack.empty();
}
/// Clears the call and unwind stacks.
void clear_stacks() {
call_stack.clear();
unwind_stack.clear();
path.clear();
}
bool can_run();
void on_enter(block* ptr);
void on_leave(block* ptr);
/// Checks whether `ptr` has been activated this run, i.e., whether we can
/// find it in `unwind_stack`.
bool activated(block* ptr) {
return std::find(path.begin(), path.end(), ptr) != path.end();
}
/// Stores the current execution stack for the run.
std::vector<block*> call_stack;
/// Stores the steps that finished execution this run.
std::vector<block*> unwind_stack;
/// Stores all steps that we have reached at least once during the run.
std::vector<block*> path;
/// Stores all steps of the test with their run-time ID.
std::map<int, std::unique_ptr<block>> steps;
template <class T>
T* get(int id, std::string_view description,
const detail::source_location& loc) {
auto& result = steps[id];
if (!result) {
result = std::make_unique<T>(shared_from_this(), id, description, loc);
}
return static_cast<T*>(result.get());
}
template <class T>
T* find_predecessor(int caller_id) {
return static_cast<T*>(find_predecessor_block(caller_id, T::type_token));
}
private:
block* find_predecessor_block(int caller_id, block_type type);
};
/// A smart pointer to the execution context of a test.
using context_ptr = std::shared_ptr<context>;
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block_type.hpp"
#include "caf/test/fwd.hpp"
#include <memory>
#include <string_view>
namespace caf::test {
/// A factory for creating runnable test definitions.
class factory {
public:
friend class registry;
factory(std::string_view suite_name, std::string_view description,
block_type type)
: suite_name_(suite_name), description_(description), type_(type) {
// nop
}
virtual ~factory();
/// Returns the name of the suite this factory belongs to.
std::string_view suite_name() const noexcept {
return suite_name_;
}
/// Creates a new runnable definition for the test.
virtual std::unique_ptr<runnable> make(context_ptr state) = 0;
protected:
std::unique_ptr<factory> next_;
std::string_view suite_name_;
std::string_view description_;
block_type type_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <memory>
namespace caf::test {
enum class block_type;
enum class binary_predicate;
enum class unary_predicate;
class and_given;
class and_then;
class and_when;
class block;
class but;
class context;
class factory;
class given;
class nesting_error;
class registry;
class reporter;
class runnable;
class runner;
class scenario;
class scope;
class section;
class test;
class then;
class when;
using context_ptr = std::shared_ptr<context>;
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
namespace caf::test {
/// Represents a `GIVEN` block.
class given : public block {
public:
using block::block;
block_type type() const noexcept override;
when* get_when(int id, std::string_view description,
const detail::source_location& loc) override;
and_when* get_and_when(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/test/block_type.hpp"
namespace caf::test {
/// Thrown when a block is opened in an invalid parent. When
/// `CAF_ENABLE_EXCEPTIONS` is off, the `raise` functions terminate the program
/// instead.
class CAF_TEST_EXPORT nesting_error {
public:
constexpr nesting_error(const nesting_error&) noexcept = default;
constexpr nesting_error& operator=(const nesting_error&) noexcept = default;
/// Returns a human-readable error message.
std::string message() const;
/// Returns the type of the parent in which the error occurred.
constexpr block_type parent() const noexcept {
return parent_;
}
/// Returns the type of the block that caused the error.
constexpr block_type child() const noexcept {
return child_;
}
/// Returns the source location of the error.
constexpr const detail::source_location& location() const noexcept {
return loc_;
}
/// Throws a `nesting_error` to indicate that `child` is not allowed in
/// `parent`.
[[noreturn]] static void
raise_not_allowed(block_type parent, block_type child,
const detail::source_location& loc
= detail::source_location::current()) {
raise_impl(code::not_allowed, parent, child, loc);
}
/// Throws a `nesting_error` to indicate that `parent` is not allowing
/// additional blocks of type `child`.
[[noreturn]] static void
raise_too_many(block_type parent, block_type child,
const detail::source_location& loc
= detail::source_location::current()) {
raise_impl(code::too_many, parent, child, loc);
}
/// Throws a `nesting_error` to indicate that `child` expected a `parent`
/// block prior to it.
[[noreturn]] static void
raise_invalid_sequence(block_type parent, block_type child,
const detail::source_location& loc
= detail::source_location::current()) {
raise_impl(code::invalid_sequence, parent, child, loc);
}
private:
enum class code {
not_allowed,
too_many,
invalid_sequence,
};
constexpr nesting_error(code what, block_type parent, block_type child,
const detail::source_location& loc) noexcept
: code_(what), parent_(parent), child_(child), loc_(loc) {
// nop
}
[[noreturn]] static void raise_impl(code what, block_type parent,
block_type child,
const detail::source_location& loc);
code code_;
block_type parent_;
block_type child_;
detail::source_location loc_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block_type.hpp"
#include "caf/test/factory.hpp"
#include "caf/test/fwd.hpp"
#include "caf/unordered_flat_map.hpp"
#include <cstddef>
#include <map>
#include <string_view>
namespace caf::test {
/// A registry for our factories.
class registry {
public:
/// Maps test names to factories. Elements are sorted by the order of their
/// registration.
using tests_map = unordered_flat_map<std::string_view, factory*>;
/// Maps suite names to suites.
using suites_map = std::map<std::string_view, tests_map>;
static suites_map suites();
template <class TestImpl>
static ptrdiff_t add(std::string_view suite_name,
std::string_view description, block_type type) {
struct impl : factory {
using factory::factory;
std::unique_ptr<runnable> make(context_ptr state) override {
return std::make_unique<TestImpl>(std::move(state), this->description_,
this->type_);
}
};
auto ptr = std::make_unique<impl>(suite_name, description, type);
auto result = reinterpret_cast<ptrdiff_t>(ptr.get());
if (head_ == nullptr) {
head_ = std::move(ptr);
tail_ = head_.get();
} else {
tail_->next_ = std::move(ptr);
tail_ = tail_->next_.get();
}
return result;
}
private:
static std::unique_ptr<factory> head_;
static factory* tail_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/test/fwd.hpp"
#include <cstddef>
#include <string_view>
namespace caf::test {
/// Observes the execution of test suites and reports the results.
class CAF_TEST_EXPORT reporter {
public:
struct stats {
size_t passed;
size_t failed;
size_t total() const noexcept {
return passed + failed;
}
stats& operator+=(const stats& other) {
passed += other.passed;
failed += other.failed;
return *this;
}
};
virtual ~reporter();
virtual bool success() const noexcept = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void begin_suite(std::string_view name) = 0;
virtual void end_suite(std::string_view name) = 0;
virtual void begin_test(context_ptr state, std::string_view) = 0;
virtual void end_test() = 0;
virtual void begin_step(block* ptr) = 0;
virtual void end_step(block* ptr) = 0;
virtual void pass(const detail::source_location& location) = 0;
/// Reports a failed check with a binary predicate.
virtual void fail(binary_predicate type, std::string_view lhs,
std::string_view rhs,
const detail::source_location& location)
= 0;
/// Reports a failed check (unary predicate).
virtual void
fail(std::string_view arg, const detail::source_location& location)
= 0;
virtual void unhandled_exception(std::string_view msg) = 0;
virtual void unhandled_exception(std::string_view msg,
const detail::source_location& location)
= 0;
virtual void
info(std::string_view msg, const detail::source_location& location)
= 0;
virtual void verbosity(unsigned level) = 0;
/// Returns statistics for the current test.
virtual stats test_stats() = 0;
/// Overrides the statistics for the current test.
virtual void test_stats(stats) = 0;
/// Returns statistics for the current suite.
virtual stats suite_stats() = 0;
/// Returns statistics for the entire run.
virtual stats total_stats() = 0;
/// Stores a pointer to the currently active reporter.
static reporter* instance;
/// Creates a default reporter that writes to the standard output.
static std::unique_ptr<reporter> make_default();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/config.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/detail/format.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/test/binary_predicate.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/fwd.hpp"
#include "caf/test/reporter.hpp"
#include <string_view>
namespace caf::test {
/// A runnable definition of a test case or scenario.
class runnable {
public:
/// Creates a new runnable.
/// @param ctx The test context.
/// @param description A description of the test or scenario.
/// @param root_type The type of the root block.
/// @param loc The source location of the test or scenario.
runnable(context_ptr ctx, std::string_view description, block_type root_type,
const detail::source_location& loc
= detail::source_location::current())
: ctx_(std::move(ctx)),
description_(description),
root_type_(root_type),
loc_(loc) {
// nop
}
virtual ~runnable();
/// Runs the next branch of the test.
void run();
/// Generates a message with the INFO severity level.
template <class... Ts>
void info(detail::format_string_with_location fwl, Ts&&... xs) {
if constexpr (sizeof...(Ts) > 0) {
auto msg = detail::format(fwl.value, std::forward<Ts>(xs)...);
reporter::instance->info(msg, fwl.location);
} else {
reporter::instance->info(fwl.value, fwl.location);
}
}
/// Checks whether `lhs` and `rhs` are equal.
template <class T0, class T1>
void check_eq(const T0& lhs, const T1& rhs,
const detail::source_location& location
= detail::source_location::current()) {
if (std::is_integral_v<T0> && std::is_integral_v<T1>) {
static_assert(std::is_signed_v<T0> == std::is_signed_v<T1>,
"comparing signed and unsigned integers is unsafe");
}
if (lhs == rhs) {
reporter::instance->pass(location);
return;
}
reporter::instance->fail(binary_predicate::eq, stringify(lhs),
stringify(rhs), location);
}
void check(bool value, const detail::source_location& location
= detail::source_location::current());
block& current_block();
#ifdef CAF_ENABLE_EXCEPTIONS
template <class Exception = void, class CodeBlock>
void check_throws(CodeBlock&& fn, const detail::source_location& location
= detail::source_location::current()) {
if constexpr (std::is_same_v<Exception, void>) {
try {
fn();
} catch (...) {
reporter::instance->pass(location);
return;
}
reporter::instance->fail("throws", location);
} else {
try {
fn();
} catch (const Exception&) {
reporter::instance->pass(location);
return;
} catch (...) {
reporter::instance->fail("throws Exception", location);
return;
}
reporter::instance->fail("throws Exception", location);
}
}
#endif
protected:
context_ptr ctx_;
std::string_view description_;
block_type root_type_;
detail::source_location loc_;
private:
template <class T>
std::string stringify(const T& value) {
if constexpr (std::is_convertible_v<T, std::string>) {
return std::string{value};
} else {
return caf::deep_to_string(value);
}
}
virtual void do_run() = 0;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/registry.hpp"
namespace caf::test {
/// Implements the main loop for running tests.
class runner {
public:
/// Bundles the result of a command line parsing operation.
struct parse_cli_result {
/// Stores whether parsing the command line arguments was successful.
bool ok;
/// Stores whether a help text was printed.
bool help_printed;
};
runner();
int run(int argc, char** argv);
private:
parse_cli_result parse_cli(int argc, char** argv);
registry::suites_map suites_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/pp.hpp"
#include "caf/test/and_given.hpp"
#include "caf/test/and_then.hpp"
#include "caf/test/and_when.hpp"
#include "caf/test/block.hpp"
#include "caf/test/context.hpp"
#include "caf/test/factory.hpp"
#include "caf/test/given.hpp"
#include "caf/test/registry.hpp"
#include "caf/test/runnable.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/suite.hpp"
#include "caf/test/then.hpp"
#include "caf/test/when.hpp"
namespace caf::test {
class scenario : public block {
public:
using block::block;
block_type type() const noexcept override;
given* get_given(int id, std::string_view description,
const detail::source_location& loc) override;
and_given* get_and_given(int id, std::string_view description,
const detail::source_location& loc) override;
when* get_when(int id, std::string_view description,
const detail::source_location& loc) override;
and_when* get_and_when(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
#define GIVEN(description) \
for (auto given_scope \
= this->current_block().get_given(__COUNTER__, description)->commit(); \
given_scope; given_scope.leave())
#define AND_GIVEN(description) \
static_cast<void>(0); \
for (auto and_given_scope = this->current_block() \
.get_and_given(__COUNTER__, description) \
->commit(); \
and_given_scope.leave())
#define WHEN(description) \
for (auto when_scope \
= this->current_block().get_when(__COUNTER__, description)->commit(); \
when_scope; when_scope.leave())
#define AND_WHEN(description) \
static_cast<void>(0); \
for (auto and_when_scope = this->current_block() \
.get_and_when(__COUNTER__, description) \
->commit(); \
and_when_scope; and_when_scope.leave())
#define THEN(description) \
for (auto then_scope \
= this->current_block().get_then(__COUNTER__, description)->commit(); \
then_scope; then_scope.leave())
#define AND_THEN(description) \
static_cast<void>(0); \
for (auto and_then_scope = this->current_block() \
.get_and_then(__COUNTER__, description) \
->commit(); \
and_then_scope; and_then_scope.leave())
#define SCENARIO(description) \
struct CAF_PP_UNIFYN(scenario_) \
: caf::test::runnable, caf_test_case_auto_fixture { \
using super = caf::test::runnable; \
using super::super; \
void do_run() override; \
static ptrdiff_t register_id; \
using caf_test_suite_name_t = std::decay_t<decltype(caf_test_suite_name)>; \
static_assert(!std::is_same_v<caf::unit_t, caf_test_suite_name_t>, \
"SCENARIO must be nested in a SUITE block"); \
}; \
ptrdiff_t CAF_PP_UNIFYN(scenario_)::register_id \
= caf::test::registry::add<CAF_PP_UNIFYN(scenario_)>( \
caf_test_suite_name, description, caf::test::block_type::scenario); \
void CAF_PP_UNIFYN(scenario_)::do_run()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/test_export.hpp"
#include "caf/test/fwd.hpp"
namespace caf::test {
/// Represents an execution scope for a test block.
class CAF_TEST_EXPORT scope {
public:
scope() noexcept = default;
explicit scope(block* ptr) noexcept : ptr_(ptr) {
// nop
}
scope(scope&& other) noexcept : ptr_(other.release()) {
// nop
}
scope& operator=(scope&& other) noexcept;
scope(const scope&) = delete;
scope& operator=(const scope&) = delete;
~scope();
/// Leave the scope with calling `on_leave` before `leave`. This allows the
/// block to perform sanity checks and potentially throws.
void leave();
/// Checks whether this scope is active.
explicit operator bool() const noexcept {
return ptr_ != nullptr;
}
private:
block* release() noexcept {
auto tmp = ptr_;
ptr_ = nullptr;
return tmp;
}
block* ptr_ = nullptr;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
namespace caf::test {
class section : public block {
public:
using block::block;
block_type type() const noexcept override;
section* get_section(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
block_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_typeblock_type
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/pp.hpp"
#include "caf/unit.hpp"
#include <string_view>
[[maybe_unused]] constexpr caf::unit_t caf_test_suite_name = caf::unit;
struct caf_test_case_auto_fixture {};
#define SUITE(name) \
namespace { \
static_assert( \
std::is_same_v<std::decay_t<decltype(caf_test_suite_name)>, caf::unit_t>, \
"only one SUITE per translation unit is supported"); \
constexpr std::string_view caf_test_suite_name = name; \
} \
namespace
#define WITH_FIXTURE(name) \
namespace CAF_PP_UNIFYN(caf_fixture_) { \
static_assert( \
!std::is_same_v<std::decay_t<decltype(caf_test_suite_name)>, caf::unit_t>, \
"WITH_FIXTURE must be placed into a SUITE scope"); \
using caf_test_case_auto_fixture = name; \
} \
namespace CAF_PP_UNIFYN(caf_fixture_)
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/pp.hpp"
#include "caf/test/block.hpp"
#include "caf/test/context.hpp"
#include "caf/test/factory.hpp"
#include "caf/test/registry.hpp"
#include "caf/test/runnable.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/section.hpp"
#include "caf/test/suite.hpp"
namespace caf::test {
/// Represents a `TEST` block.
class test : public block {
public:
using block::block;
block_type type() const noexcept override;
section* get_section(int id, std::string_view description,
const detail::source_location& loc) override;
scope commit();
};
} // namespace caf::test
#define SECTION(description) \
for (auto CAF_PP_UNIFYN(scope_) = this->current_block() \
.get_section(__COUNTER__, description) \
->commit(); \
CAF_PP_UNIFYN(scope_); CAF_PP_UNIFYN(scope_).leave())
#define TEST(description) \
struct CAF_PP_UNIFYN(test_) \
: caf::test::runnable, caf_test_case_auto_fixture { \
using super = caf::test::runnable; \
using super::super; \
void do_run() override; \
static ptrdiff_t register_id; \
using caf_test_suite_name_t = std::decay_t<decltype(caf_test_suite_name)>; \
static_assert(!std::is_same_v<caf::unit_t, caf_test_suite_name_t>, \
"TEST must be nested in a SUITE block"); \
}; \
ptrdiff_t CAF_PP_UNIFYN(test_)::register_id \
= caf::test::registry::add<CAF_PP_UNIFYN(test_)>( \
caf_test_suite_name, description, caf::test::block_type::test); \
void CAF_PP_UNIFYN(test_)::do_run()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
#include "caf/test/block_type.hpp"
namespace caf::test {
/// Represents a `THEN` block.
class then : public block {
public:
using block::block;
static constexpr block_type type_token = block_type::then;
block_type type() const noexcept override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/block.hpp"
#include "caf/test/block_type.hpp"
namespace caf::test {
class when : public block {
public:
using block::block;
static constexpr block_type type_token = block_type::when;
then* get_then(int id, std::string_view description,
const detail::source_location& loc) override ;
and_then* get_and_then(int id, std::string_view description,
const detail::source_location& loc) override;
block_type type() const noexcept override;
scope commit();
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/and_given.hpp"
#include "caf/test/and_when.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/when.hpp"
namespace caf::test {
block_type and_given::type() const noexcept {
return block_type::and_given;
}
when* and_given::get_when(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<when>(id, description, loc);
}
and_when* and_given::get_and_when(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<and_when>(id, description, loc);
if (nested_.empty()) {
nesting_error::raise_invalid_sequence(block_type::when,
block_type::and_when, loc);
}
nested_.push_back(result);
return result;
}
scope and_given::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/and_then.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/then.hpp"
namespace caf::test {
block_type and_then::type() const noexcept {
return block_type::and_then;
}
scope and_then::commit() {
// An AND_THEN block is only executed if the previous THEN block was executed.
if (!can_run() || !ctx_->activated(ctx_->find_predecessor<then>(id_)))
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/and_when.hpp"
#include "caf/test/and_then.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/then.hpp"
#include "caf/test/when.hpp"
namespace caf::test {
block_type and_when::type() const noexcept {
return block_type::and_when;
}
then* and_when::get_then(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<then>(id, description, loc);
if (nested_.empty()) {
nested_.emplace_back(result);
} else if (nested_.front() != result) {
nesting_error::raise_too_many(type(), block_type::then, loc);
}
return result;
}
and_then* and_when::get_and_then(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<and_then>(id, description, loc);
if (nested_.empty()) {
nesting_error::raise_invalid_sequence(block_type::then,
block_type::and_then, loc);
}
nested_.push_back(result);
return result;
}
scope and_when::commit() {
// An AND_WHEN block is only executed if the previous WHEN block was executed.
if (!can_run() || !ctx_->activated(ctx_->find_predecessor<when>(id_)))
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/block.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
namespace caf::test {
block::block(context_ptr ctx, int id, std::string_view description,
const detail::source_location& loc)
: ctx_(ctx), id_(id), description_(description), loc_(loc) {
// nop
}
block::~block() {
// nop
}
void block::enter() {
executed_ = true;
ctx_->on_enter(this);
}
void block::leave() {
ctx_->on_leave(this);
}
void block::on_leave() {
// nop
}
bool block::can_run() const noexcept {
auto pred = [](const auto* nested) { return nested->can_run(); };
return !executed_ || std::any_of(nested_.begin(), nested_.end(), pred);
}
section* block::get_section(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::section, loc);
}
given* block::get_given(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::given, loc);
}
and_given* block::get_and_given(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::given, loc);
}
when* block::get_when(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::when, loc);
}
and_when* block::get_and_when(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::and_when, loc);
}
then* block::get_then(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::then, loc);
}
and_then* block::get_and_then(int, std::string_view,
const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::and_then, loc);
}
but* block::get_but(int, std::string_view, const detail::source_location& loc) {
nesting_error::raise_not_allowed(type(), block_type::but, loc);
}
std::unique_ptr<block>& block::get_nested_or_construct(int id) {
// Note: we only need this trivial getter to have avoid including
// "context.hpp" from "block.hpp".
return ctx_->steps[id];
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/context.hpp"
#include "caf/test/block.hpp"
#include "caf/test/reporter.hpp"
namespace caf::test {
void context::on_enter(block* ptr) {
call_stack.push_back(ptr);
unwind_stack.clear();
path.push_back(ptr);
reporter::instance->begin_step(ptr);
}
void context::on_leave(block* ptr) {
call_stack.pop_back();
unwind_stack.push_back(ptr);
reporter::instance->end_step(ptr);
}
bool context::can_run() {
auto pred = [](auto& kvp) { return kvp.second->can_run(); };
return !steps.empty() && std::any_of(steps.begin(), steps.end(), pred);
}
block* context::find_predecessor_block(int caller_id, block_type type) {
// Find the caller.
auto i = steps.find(caller_id);
if (i == steps.end())
return nullptr;
// Find the first step of type `T` that precedes the caller.
auto pred = [type](auto& kvp) { return kvp.second->type() == type; };
auto j = std::find_if(std::reverse_iterator{i}, steps.rend(), pred);
if (j == steps.rend())
return nullptr;
return j->second.get();
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/factory.hpp"
namespace caf::test {
factory::~factory() {
// nop
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/given.hpp"
#include "caf/test/and_when.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/when.hpp"
namespace caf::test {
block_type given::type() const noexcept {
return block_type::given;
}
when* given::get_when(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<when>(id, description, loc);
}
and_when* given::get_and_when(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<and_when>(id, description, loc);
if (nested_.empty()) {
nesting_error::raise_invalid_sequence(block_type::when,
block_type::and_when, loc);
}
nested_.push_back(result);
return result;
}
scope given::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/nesting_error.hpp"
#include "caf/config.hpp"
#include "caf/detail/format.hpp"
#include <cstdio>
namespace caf::test {
std::string nesting_error::message() const {
switch (code_) {
default: // not allowed
return detail::format("cannot nest a {} in a {} block",
macro_name(child_), macro_name(parent_));
case code::too_many:
return detail::format("too many {} blocks in a {} block",
macro_name(child_), macro_name(parent_));
case code::invalid_sequence:
return detail::format("need a {} block before a {} block",
macro_name(parent_), macro_name(child_));
}
}
[[noreturn]] void
nesting_error::raise_impl(nesting_error::code what, block_type parent,
block_type child,
const detail::source_location& loc) {
#ifdef CAF_ENABLE_EXCEPTIONS
throw nesting_error{what, parent, child, loc};
#else
auto msg = nesting_error{code::not_allowed, parent, child}.message();
fprintf(stderr, "[FATAL] critical error (%s:%d): %s\n", msg.c_str(),
loc.file_name, static_cast<int>(loc.line));
abort();
#endif
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/registry.hpp"
#include "caf/detail/format.hpp"
#include "caf/raise_error.hpp"
namespace caf::test {
registry::suites_map registry::suites() {
suites_map result;
for (auto ptr = head_.get(); ptr != nullptr; ptr = ptr->next_.get()) {
auto& suite = result[ptr->suite_name_];
if (auto [iter, ok] = suite.emplace(ptr->description_, ptr); !ok) {
auto msg = detail::format("duplicate test name in suite {}: {}",
ptr->suite_name(), ptr->description_);
CAF_RAISE_ERROR(std::logic_error, msg.c_str());
}
}
return result;
}
std::unique_ptr<factory> registry::head_;
factory* registry::tail_;
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/reporter.hpp"
#include "caf/detail/format.hpp"
#include "caf/detail/log_level.hpp"
#include "caf/raise_error.hpp"
#include "caf/term.hpp"
#include "caf/test/binary_predicate.hpp"
#include "caf/test/block.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include <iostream>
namespace caf::test {
reporter::~reporter() {
// nop
}
reporter* reporter::instance;
namespace {
/// Implements a mini-DSL for colored output:
/// - $R(red text)
/// - $G(green text)
/// - $B(blue text)
/// - $Y(yellow text)
/// - $M(magenta text)
/// - $C(cyan text)
/// - $0 turns off coloring completely (enter verbatim mode)
struct colorizing_iterator {
enum mode_t {
normal,
read_color,
escape,
color,
off,
off_read_color,
off_escape,
off_color,
verbatim,
};
mode_t mode = normal;
std::ostream* out;
void put(char c) {
switch (mode) {
case normal:
if (c == '$')
mode = read_color;
else
out->put(c);
break;
case read_color:
out->flush();
switch (c) {
case 'R':
*out << term::red;
break;
case 'G':
*out << term::green;
break;
case 'B':
*out << term::blue;
break;
case 'Y':
*out << term::yellow;
break;
case 'M':
*out << term::magenta;
break;
case 'C':
*out << term::cyan;
break;
case '0':
mode = verbatim;
return;
default:
CAF_RAISE_ERROR("invalid color code");
break;
}
mode = escape;
break;
case escape:
if (c != '(')
CAF_RAISE_ERROR("expected ( after color code");
mode = color;
break;
case color:
if (c == ')') {
out->flush();
*out << term::reset;
mode = normal;
break;
}
out->put(c);
break;
case off:
if (c == '$')
mode = off_read_color;
else
out->put(c);
break;
case off_read_color:
mode = off_escape;
break;
case off_escape:
if (c != '(')
CAF_RAISE_ERROR("expected ( after color code");
mode = color;
break;
case off_color:
if (c == ')') {
mode = off;
break;
}
out->put(c);
break;
default: // verbatim
out->put(c);
}
}
colorizing_iterator& operator++() {
return *this;
}
colorizing_iterator operator++(int) {
return *this;
}
colorizing_iterator& operator*() {
return *this;
}
colorizing_iterator& operator=(char c) {
put(c);
return *this;
}
};
class default_reporter : public reporter {
public:
using clock_type = std::chrono::steady_clock;
using time_point = clock_type::time_point;
using fractional_seconds = std::chrono::duration<double>;
bool success() const noexcept override {
return total_stats_.failed == 0;
}
void start() override {
start_time_ = clock_type::now();
}
auto plain() {
return std::ostream_iterator<char>{std::cout};
}
auto colored() {
return colorizing_iterator{colorizing_iterator::normal, &std::cout};
}
void stop() override {
using detail::format_to;
using std::chrono::duration_cast;
auto elapsed = clock_type::now() - start_time_;
format_to(colored(),
"$B(Summary):\n"
" $B(Time): $Y({0:.3f}s)\n"
" $B(Suites): ${1}({2} / {3})\n"
" $B(Checks): ${1}({4} / {5})\n"
" $B(Status): ${1}({6})\n",
duration_cast<fractional_seconds>(elapsed).count(), // {0}
total_stats_.failed > 0 ? 'R' : 'G', // {1}
num_suites_ - failed_suites_.size(), // {2}
num_suites_, // {3}
total_stats_.passed, // {4}
total_stats_.total(), // {5}
total_stats_.failed > 0 ? "failed" : "passed"); // {6}
if (!failed_suites_.empty()) {
format_to(colored(), " $B(Failed Suites):\n");
for (auto name : failed_suites_)
format_to(colored(), " - $R({})\n", name);
}
std::cout << std::endl;
}
void begin_suite(std::string_view name) override {
failed_tests_.clear();
current_suite_ = name;
suite_start_time_ = clock_type::now();
++num_suites_;
suite_stats_ = {0, 0};
}
void end_suite(std::string_view name) override {
using detail::format_to;
using std::chrono::duration_cast;
total_stats_ += suite_stats_;
if (suite_stats_.failed > 0)
failed_suites_.push_back(name);
else if (level_ < CAF_LOG_LEVEL_DEBUG)
return;
auto elapsed = clock_type::now() - suite_start_time_;
format_to(colored(),
"$B(Suite Summary): $C({0})\n"
" $B(Time): $Y({1:.3f}s)\n"
" $B(Checks): ${2}({3} / {4})\n"
" $B(Status): ${2}({5})\n",
name, // {0}
duration_cast<fractional_seconds>(elapsed).count(), // {1}
suite_stats_.failed > 0 ? 'R' : 'G', // {2}
suite_stats_.passed, // {3}
suite_stats_.total(), // {4}
suite_stats_.failed > 0 ? "failed" : "passed"); // {5}
if (!failed_tests_.empty()) {
format_to(colored(), " $B(Failed tests):\n");
for (auto name : failed_tests_)
format_to(colored(), " - $R({})\n", name);
}
std::cout << std::endl;
}
void begin_test(context_ptr state, std::string_view name) override {
live_ = false;
test_stats_ = {0, 0};
current_test_ = name;
current_ctx_ = std::move(state);
}
void end_test() override {
if (test_stats_.failed > 0)
failed_tests_.push_back(current_test_);
suite_stats_ += test_stats_;
current_ctx_.reset();
if(live_)
std::cout << std::endl;
}
void set_live() {
if (live_)
return;
using detail::format_to;
if (current_ctx_ == nullptr)
CAF_RAISE_ERROR(std::logic_error, "begin_test was not called");
format_to(colored(), "$C(Suite): $0{}\n", current_suite_);
indent_ = 2;
live_= true;
for (auto* frame : current_ctx_->call_stack)
begin_step(frame);
}
void begin_step(block* ptr) override {
using detail::format_to;
if (!live_)
return;
if (indent_ > 0 && is_extension(ptr->type()))
indent_ -= 2;
format_to(colored(), "{0:{1}}$C({2}): $0{3}\n", ' ', indent_,
as_prefix(ptr->type()), ptr->description());
indent_ += 2;
}
void end_step(block* ptr) override {
if (!live_)
return;
if (indent_ == 0)
CAF_RAISE_ERROR("unbalanced (begin|end)_step calls");
if (!is_extension(ptr->type()))
indent_ -= 2;
}
void pass(const detail::source_location& location) override {
using detail::format_to;
test_stats_.passed++;
if (level_ < CAF_LOG_LEVEL_DEBUG)
return;
set_live();
format_to(colored(), "{0:{1}}$G(pass) $C({2}):$Y({3})$0\n", ' ', indent_,
location.file_name(), location.line());
}
void fail(binary_predicate type, std::string_view lhs, std::string_view rhs,
const detail::source_location& location) override {
using detail::format_to;
test_stats_.failed++;
if (level_ < CAF_LOG_LEVEL_ERROR)
return;
set_live();
format_to(colored(),
"{0:{1}}$R(error): lhs {2} rhs\n"
"{0:{1}} loc: $C({3}):$Y({4})$0\n"
"{0:{1}} lhs: {5}\n"
"{0:{1}} rhs: {6}\n",
' ', indent_, str(negate(type)), location.file_name(),
location.line(), lhs, rhs);
}
void fail(std::string_view arg,
const detail::source_location& location) override {
using detail::format_to;
test_stats_.failed++;
if (level_ < CAF_LOG_LEVEL_ERROR)
return;
set_live();
format_to(colored(),
"{0:{1}}$R(error): check failed\n"
"{0:{1}} loc: $C({3}):$Y({4})$0\n"
"{0:{1}} check: {5}\n",
' ', indent_, location.file_name(), location.line(), arg);
}
void unhandled_exception(std::string_view msg) override {
using detail::format_to;
test_stats_.failed++;
if (level_ < CAF_LOG_LEVEL_ERROR)
return;
set_live();
if (current_ctx_ == nullptr || current_ctx_->unwind_stack.empty()) {
format_to(colored(),
"{0:{1}}$R(unhandled exception): abort test run\n"
"{0:{1}} loc: $R(unknown)$0\n"
"{0:{1}} msg: {2}\n",
' ', indent_, msg);
} else {
auto& location = current_ctx_->unwind_stack.front()->location();
format_to(colored(),
"{0:{1}}$R(unhandled exception): abort test run\n"
"{0:{1}} loc: in block starting at $C({2}):$Y({3})$0\n"
"{0:{1}} msg: {4}\n",
' ', indent_, location.file_name(), location.line(), msg);
}
}
void unhandled_exception(std::string_view msg,
const detail::source_location& location) override {
using detail::format_to;
test_stats_.failed++;
if (level_ < CAF_LOG_LEVEL_ERROR)
return;
set_live();
format_to(colored(),
"{0:{1}}$R(unhandled exception): abort test run\n"
"{0:{1}} loc: $C({2}):$Y({3})$0\n"
"{0:{1}} msg: {4}\n",
' ', indent_, location.file_name(), location.line(), msg);
}
void info(std::string_view msg,
const detail::source_location& location) override {
using detail::format_to;
if (level_ < CAF_LOG_LEVEL_INFO)
return;
set_live();
format_to(colored(),
"{0:{1}}$M(info):\n"
"{0:{1}} loc: $C({2}):$Y({3})$0\n"
"{0:{1}} msg: {4}\n",
' ', indent_, location.file_name(), location.line(), msg);
}
void verbosity(unsigned level) override {
level_ = level;
}
stats test_stats() override {
return test_stats_;
}
void test_stats(stats new_value) override {
test_stats_ = new_value;
}
stats suite_stats() override {
return suite_stats_;
}
stats total_stats() override {
return total_stats_;
}
private:
void print_indent(size_t indent) {
for (size_t i = 0; i < indent; ++i)
std::cout << ' ';
}
void print_indent() {
print_indent(indent_);
}
/// Configures the number of spaces to print before each line.
size_t indent_ = 0;
/// Stores statistics for the current test.
stats test_stats_ = {0, 0};
/// Stores statistics for the current suite.
stats suite_stats_ = {0, 0};
/// Stores statistics for all suites.
stats total_stats_ = {0, 0};
/// Counts the number of test suites.
size_t num_suites_ = 0;
/// Stores the time point when the test runner started.
time_point start_time_;
/// Stores the time point when the current test suite started.
time_point suite_start_time_;
/// Configures the verbosity of the reporter.
unsigned level_ = CAF_LOG_LEVEL_INFO;
/// Stores the names of failed test suites.
std::vector<std::string_view> failed_suites_;
/// Stores the names of failed tests in a suite.
std::vector<std::string_view> failed_tests_;
/// Stores whether we render the current test as live. We start off with
/// `false` and switch to `true` as soon as the current test generates any
/// output.
bool live_ = false;
/// Stores the name of the current test suite.
std::string_view current_suite_;
/// Stores the name of the current test suite.
std::string_view current_test_;
/// Stores the state for the current test.
context_ptr current_ctx_;
};
} // namespace
std::unique_ptr<reporter> reporter::make_default() {
return std::make_unique<default_reporter>();
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/runnable.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/scenario.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/test.hpp"
namespace caf::test {
runnable::~runnable() {
// nop
}
void runnable::run() {
switch (root_type_) {
case block_type::scenario:
if (auto guard = ctx_->get<scenario>(0, description_, loc_)->commit()) {
do_run();
return;
}
CAF_RAISE_ERROR(std::logic_error,
"failed to select the root block for the scenario");
break;
case block_type::test:
if (auto guard = ctx_->get<test>(0, description_, loc_)->commit()) {
do_run();
return;
}
CAF_RAISE_ERROR(std::logic_error,
"failed to select the root block for the test");
break;
default:
CAF_RAISE_ERROR(std::logic_error, "invalid root type");
}
}
void runnable::check(bool value, const detail::source_location& location) {
if (value) {
reporter::instance->pass(location);
return;
}
reporter::instance->fail("should be true", location);
}
block& runnable::current_block() {
if (ctx_->call_stack.empty())
CAF_RAISE_ERROR(std::logic_error, "no current block");
return *ctx_->call_stack.back();
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/runner.hpp"
#include "caf/config_option.hpp"
#include "caf/config_option_adder.hpp"
#include "caf/config_option_set.hpp"
#include "caf/detail/log_level.hpp"
#include "caf/settings.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/reporter.hpp"
#include "caf/test/runnable.hpp"
#include <iostream>
#include <optional>
#include <string>
namespace caf::test {
namespace {
config_option_set make_option_set() {
config_option_set result;
config_option_adder{result, "global"}
.add<bool>("help,h?", "print this help text")
.add<bool>("available-suites,a", "print all available 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) {
if (x == "quiet")
return CAF_LOG_LEVEL_QUIET;
if (x == "error")
return CAF_LOG_LEVEL_ERROR;
if (x == "warning")
return CAF_LOG_LEVEL_WARNING;
if (x == "info")
return CAF_LOG_LEVEL_INFO;
if (x == "debug")
return CAF_LOG_LEVEL_DEBUG;
if (x == "trace")
return CAF_LOG_LEVEL_TRACE;
return {};
}
}//
runner::runner() : suites_(caf::test::registry::suites()) {
// nop
}
int runner::run(int argc, char** argv) {
auto default_reporter = reporter::make_default();
reporter::instance = default_reporter.get();
if (auto [ok, help_printed] = parse_cli(argc, argv); !ok) {
return EXIT_FAILURE;
} else if (help_printed) {
return EXIT_SUCCESS;
}
default_reporter->start();
for (auto& [suite_name, suite] : suites_) {
default_reporter->begin_suite(suite_name);
for (auto [test_name, factory_instance] : suite) {
auto state = std::make_shared<context>();
try {
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());
} 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);
}
default_reporter->stop();
return default_reporter->success() > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
runner::parse_cli_result runner::parse_cli(int argc, char** argv) {
using detail::format_to;
caf::settings cfg;
std::vector<std::string> args_cpy{argv + 1, argv + argc};
auto options = make_option_set();
auto res = options.parse(cfg, args_cpy);
auto err = std::ostream_iterator<char>{std::cerr};
if (res.first != caf::pec::success) {
format_to(err, "error while parsing argument '{}': {}\n\n{}\n",
*res.second, 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};
}
format_to(err, "available tests in suite {}:\n", i->first);
for (const auto& [test_name, factory_instance] : i->second)
format_to(err, "- {}\n", test_name);
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};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/scenario.hpp"
#include "caf/test/and_given.hpp"
#include "caf/test/and_when.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/given.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/when.hpp"
namespace caf::test {
block_type scenario::type() const noexcept {
return block_type::scenario;
}
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) {
return get_nested<and_given>(id, description, loc);
}
when* scenario::get_when(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<when>(id, description, loc);
}
and_when* scenario::get_and_when(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<and_when>(id, description, loc);
}
scope scenario::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/scope.hpp"
#include "caf/test/block.hpp"
namespace caf::test {
scope& scope::operator=(scope&& other) noexcept {
if (ptr_)
ptr_->leave();
ptr_ = other.release();
return *this;
}
scope::~scope() {
if (ptr_)
ptr_->leave();
}
void scope::leave() {
if (ptr_) {
ptr_->on_leave();
ptr_->leave();
ptr_ = nullptr;
}
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/section.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/scope.hpp"
namespace caf::test {
block_type section::type() const noexcept {
return block_type::section;
}
section* section::get_section(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<section>(id, description, loc);
}
scope section::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/test.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/section.hpp"
namespace caf::test {
block_type test::type() const noexcept {
return block_type::test;
}
section* test::get_section(int id, std::string_view description,
const detail::source_location& loc) {
return get_nested<section>(id, description, loc);
}
scope test::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/then.hpp"
#include "caf/test/block_type.hpp"
#include "caf/test/context.hpp"
#include "caf/test/scope.hpp"
namespace caf::test {
block_type then::type() const noexcept {
return block_type::then;
}
scope then::commit() {
if (!ctx_->active() || executed_)
return {};
enter();
return scope{this};
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/when.hpp"
#include "caf/test/and_then.hpp"
#include "caf/test/context.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/scope.hpp"
#include "caf/test/then.hpp"
namespace caf::test {
then* when::get_then(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<then>(id, description, loc);
if (nested_.empty()) {
nested_.emplace_back(result);
} else if (nested_.front() != result) {
nesting_error::raise_too_many(type(), block_type::then, loc);
}
return result;
}
and_then* when::get_and_then(int id, std::string_view description,
const detail::source_location& loc) {
auto* result = ctx_->get<and_then>(id, description, loc);
if (nested_.empty()) {
nesting_error::raise_invalid_sequence(block_type::then,
block_type::and_then, loc);
}
nested_.push_back(result);
return result;
}
block_type when::type() const noexcept {
return block_type::when;
}
scope when::commit() {
if (!ctx_->active() || !can_run())
return {};
enter();
return scope{this};
}
} // namespace caf::test
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
// the main distribution directory for license terms and copyright or visit // the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE. // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/unit_test_impl.hpp" #include "caf/test/caf_test_main.hpp"
CAF_TEST_MAIN()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/block_type.hpp"
#include "caf/test/test.hpp"
using caf::test::block_type;
SUITE("caf.test.block_type") {
TEST("is_extension checks whether a block type needs a predecessor") {
using caf::test::is_extension;
SECTION("is_extension is true for all AND_* types and BUT") {
check(is_extension(block_type::and_given));
check(is_extension(block_type::and_when));
check(is_extension(block_type::and_then));
check(is_extension(block_type::but));
}
SECTION("is_extension is false for regular types") {
check(!is_extension(block_type::test));
check(!is_extension(block_type::section));
check(!is_extension(block_type::scenario));
check(!is_extension(block_type::given));
check(!is_extension(block_type::when));
check(!is_extension(block_type::then));
}
}
} // SUITE("caf.test.block_type")
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/scenario.hpp"
#include "caf/config.hpp"
#include "caf/test/nesting_error.hpp"
#include "caf/test/test.hpp"
SUITE("caf.test.scenario") {
#ifdef CAF_ENABLE_EXCEPTIONS
SCENARIO("a scenario may not contain a section") {
auto entered_section = false;
check_throws<caf::test::nesting_error>([this, &entered_section] {
GIVEN("given-1") {
WHEN("when-1") {
SECTION("nesting error") {
entered_section = true;
}
}
}
});
check(!entered_section);
}
#endif
SCENARIO("each run starts with fresh local variables") {
GIVEN("a my_int variable") {
auto my_int = 0;
WHEN("entering a WHEN block") {
THEN("the local variable has its default value") {
check_eq(my_int, 0);
my_int = 42;
check_eq(my_int, 42);
}
}
WHEN("entering another WHEN block") {
THEN("previous writes to the local variable are gone") {
check_eq(my_int, 0);
}
}
}
}
struct int_fixture {
int my_int = 0;
};
WITH_FIXTURE(int_fixture) {
SCENARIO("each run starts with a fresh fixture") {
GIVEN("a fixture with a my_int member variable") {
WHEN("entering a WHEN block") {
THEN("the fixture is default-constructed") {
check_eq(my_int, 0);
my_int = 42;
check_eq(my_int, 42);
}
}
WHEN("entering another WHEN block") {
THEN("previous writes to the fixture are gone") {
check_eq(my_int, 0);
}
}
}
}
} // WITH_FIXTURE(int_fixture)
SCENARIO("scenario-1") {
auto render = [this]() -> std::string {
if (ctx_->call_stack.empty())
return "nil";
std::string result;
auto i = ctx_->call_stack.begin();
result += (*i++)->description();
while (i != ctx_->call_stack.end()) {
result += "/";
result += (*i++)->description();
}
return result;
};
auto counter = 0;
check_eq(render(), "scenario-1");
GIVEN("given-1") {
check_eq(++counter, 1);
check_eq(render(), "scenario-1/given-1");
WHEN("when-1") {
check_eq(++counter, 2);
check_eq(render(), "scenario-1/given-1/when-1");
THEN("then-1") {
check_eq(++counter, 3);
check_eq(render(), "scenario-1/given-1/when-1/then-1");
}
AND_THEN("and-then-1") {
check_eq(++counter, 4);
check_eq(render(), "scenario-1/given-1/when-1/and-then-1");
}
AND_THEN("and-then-2") {
check_eq(++counter, 5);
check_eq(render(), "scenario-1/given-1/when-1/and-then-2");
}
}
AND_WHEN("and-when-1-1") {
check_eq(++counter, 6);
check_eq(render(), "scenario-1/given-1/and-when-1-1");
}
AND_WHEN("and-when-1-2") {
check_eq(++counter, 7);
check_eq(render(), "scenario-1/given-1/and-when-1-2");
}
WHEN("when-2") {
check_eq(++counter, 2);
check_eq(render(), "scenario-1/given-1/when-2");
THEN("then-1") {
check_eq(++counter, 3);
check_eq(render(), "scenario-1/given-1/when-2/then-1");
}
AND_THEN("and-then-1") {
check_eq(++counter, 4);
check_eq(render(), "scenario-1/given-1/when-2/and-then-1");
}
AND_THEN("and-then-2") {
check_eq(++counter, 5);
check_eq(render(), "scenario-1/given-1/when-2/and-then-2");
}
}
AND_WHEN("and-when-2-1") {
check_eq(++counter, 6);
check_eq(render(), "scenario-1/given-1/and-when-2-1");
}
AND_WHEN("and-when-2-2") {
check_eq(++counter, 7);
check_eq(render(), "scenario-1/given-1/and-when-2-2");
}
}
}
} // SUITE("caf.test.scenario")
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/test.hpp"
using caf::test::block_type;
SUITE("caf.test.test") {
TEST("tests can contain checks") {
auto* rep = caf::test::reporter::instance;
for (int i = 0; i < 3; ++i)
check_eq(i, i);
auto stats = rep->test_stats();
check_eq(stats.passed, 3u);
check_eq(stats.failed, 0u);
info("this test had {} checks", rep->test_stats().total());
}
TEST("failed checks increment the failed counter") {
check_eq(1, 2);
auto stats = caf::test::reporter::instance->test_stats();
check_eq(stats.passed, 0u);
check_eq(stats.failed, 1u);
info("reset error count to not fail the test");
caf::test::reporter::instance->test_stats({2, 0});
}
TEST("each run starts with fresh local variables") {
auto my_int = 0;
SECTION("block 1 reads my_int as 0") {
check_eq(my_int, 0);
my_int = 42;
check_eq(my_int, 42);
}
SECTION("block 2 also reads my_int as 0") {
check_eq(my_int, 0);
}
}
struct int_fixture {
int my_int = 0;
};
WITH_FIXTURE(int_fixture) {
TEST("each run starts with a fresh fixture") {
SECTION("block 1 reads my_int as 0") {
check_eq(my_int, 0);
my_int = 42;
check_eq(my_int, 42);
}
SECTION("block 2 also reads my_int as 0") {
check_eq(my_int, 0);
}
}
} // WITH_FIXTURE(int_fixture)
} // SUITE("caf.test.test")
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