Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
962b23a4
Commit
962b23a4
authored
Apr 30, 2019
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'pull/833'
parents
406d78ea
131f8fc3
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
257 additions
and
2 deletions
+257
-2
doc/CMakeLists.txt
doc/CMakeLists.txt
+1
-0
doc/tex/Testing.tex
doc/tex/Testing.tex
+173
-0
doc/tex/index.rst
doc/tex/index.rst
+1
-0
doc/tex/manual.tex
doc/tex/manual.tex
+1
-0
examples/CMakeLists.txt
examples/CMakeLists.txt
+3
-0
examples/testing/ping_pong.cpp
examples/testing/ping_pong.cpp
+65
-0
libcaf_core/caf/detail/type_traits.hpp
libcaf_core/caf/detail/type_traits.hpp
+7
-0
libcaf_test/caf/test/dsl.hpp
libcaf_test/caf/test/dsl.hpp
+6
-2
No files found.
doc/CMakeLists.txt
View file @
962b23a4
...
...
@@ -31,6 +31,7 @@ set(sources
tex/TypeInspection.tex
tex/UsingAout.tex
tex/Utility.tex
tex/Testing.tex
)
# -- process .in files -----------------------------------------------------
...
...
doc/tex/Testing.tex
0 → 100644
View file @
962b23a4
\section
{
Testing
}
\label
{
testing
}
CAF comes with built-in support for writing unit tests in a domain-specific
language (DSL). The API looks similar to well-known testing frameworks such as
Boost.Test and Catch but adds CAF-specific macros for testing messaging between
actors.
Our design leverages four main concepts:
\begin{itemize}
\item
\textbf
{
Checks
}
represent single boolean expressions.
\item
\textbf
{
Tests
}
contain one or more checks.
\item
\textbf
{
Fixtures
}
equip tests with a fixed data environment.
\item
\textbf
{
Suites
}
group tests together.
\end{itemize}
The following code illustrates a very basic test case that captures the four
main concepts described above.
\begin{lstlisting}
// Adds all tests in this compilation unit to the suite "math".
#define CAF
_
SUITE math
// Pulls in all the necessary macros.
#include "caf/test/dsl.hpp"
namespace
{
struct fixture
{}
;
}
// namespace
// Makes all members of `fixture` available to tests in the scope.
CAF
_
TEST
_
FIXTURE
_
SCOPE(math
_
tests, fixture)
// Implements our first test.
CAF
_
TEST(divide)
{
CAF
_
CHECK(1 / 1 == 0); // fails
CAF
_
CHECK(2 / 2 == 1); // passes
CAF
_
REQUIRE(3 + 3 == 5); // fails and aborts test execution
CAF
_
CHECK(4 - 4 == 0); // unreachable due to previous requirement error
}
CAF
_
TEST
_
FIXTURE
_
SCOPE
_
END()
\end{lstlisting}
The code above highlights the two basic macros
\lstinline
^
CAF
_
CHECK
^
and
\lstinline
^
CAF
_
REQUIRE
^
. The former reports failed checks, but allows the test
to continue on error. The latter stops test execution if the boolean expression
evaluates to false.
The third macro worth mentioning is
\lstinline
^
CAF
_
FAIL
^
. It unconditionally
stops test execution with an error message. This is particularly useful for
stopping program execution after receiving unexpected messages, as we will see
later.
\subsection
{
Testing Actors
}
The following example illustrates how to add an actor system as well as a
scoped actor to fixtures. This allows spawning of and interacting with actors
in a similar way regular programs would. Except that we are using macros such
as
\lstinline
^
CAF
_
CHECK
^
and provide tests rather than implementing a
\lstinline
^
caf
_
main
^
.
\begin{lstlisting}
namespace
{
struct fixture
{
caf::actor
_
system
_
config cfg;
caf::actor
_
system sys;
caf::scoped
_
actor self;
fixture() : sys(cfg), self(sys)
{
// nop
}
}
;
caf::behavior adder()
{
return
{
[=](int x, int y)
{
return x + y;
}
}
;
}
}
// namespace
CAF
_
TEST
_
FIXTURE
_
SCOPE(actor
_
tests, fixture)
CAF
_
TEST(simple actor test)
{
// Our Actor-Under-Test.
auto aut = self->spawn(adder);
self->request(aut, caf::infinite, 3, 4).receive(
[=](int r)
{
CAF
_
CHECK(r == 7);
}
,
[
&
](caf::error
&
err)
{
// Must not happen, stop test.
CAF
_
FAIL(sys.render(err));
}
);
}
CAF
_
TEST
_
FIXTURE
_
SCOPE
_
END()
\end{lstlisting}
The example above works, but suffers from several issues:
\begin{itemize}
\item
Significant amount of boilerplate code.
\item
Using a scoped actor as illustrated above can only test one actor at a
time. However, messages between other actors are invisible to us.
\item
CAF runs actors in a thread pool by default. The resulting nondeterminism
makes triggering reliable ordering of messages near impossible. Further,
forcing timeouts to test error handling code is even harder.
\end{itemize}
\subsection
{
Deterministic Testing
}
CAF provides a scheduler implementation specifically tailored for writing unit
tests called
\lstinline
^
test
_
coordinator
^
. It does not start any threads and
instead gives unit tests full control over message dispatching and timeout
management.
To reduce boilerplate code, CAF also provides a fixture template called
\lstinline
^
test
_
coordinator
_
fixture
^
that comes with ready-to-use actor system
(
\lstinline
^
sys
^
) and testing scheduler (
\lstinline
^
sched
^
). The optional
template parameter allows unit tests to plugin custom actor system
configuration classes.
Using this fixture unlocks three additional macros:
\begin{itemize}
\item
\lstinline
^
expect
^
checks for a single message. The macro verifies the
content types of the message and invokes the necessary member functions on
the test coordinator. Optionally, the macro checks the receiver of the
message and its content. If the expected message does not exist, the test
aborts.
\item
\lstinline
^
allow
^
is similar to
\lstinline
^
expect
^
, but it does not abort
the test if the expected message is missing. This macro returns
\lstinline
^
true
^
if the allowed message was delivered,
\lstinline
^
false
^
otherwise.
\item
\lstinline
^
disallow
^
aborts the test if a particular message was delivered
to an actor.
\end{itemize}
The following example implements two actors,
\lstinline
^
ping
^
and
\lstinline
^
pong
^
, that exchange a configurable amount of messages. The test
\emph
{
three pings
}
then checks the contents of each message with
\lstinline
^
expect
^
and verifies that no additional messages exist using
\lstinline
^
disallow
^
.
\cppexample
[12-65]
{
testing/ping
_
pong
}
doc/tex/index.rst
View file @
962b23a4
...
...
@@ -19,6 +19,7 @@
GroupCommunication
ManagingGroupsOfWorkers
Streaming
Testing
.. toctree::
:maxdepth: 2
...
...
doc/tex/manual.tex
View file @
962b23a4
...
...
@@ -202,6 +202,7 @@ User Manual\\
\include
{
tex/GroupCommunication
}
\include
{
tex/ManagingGroupsOfWorkers
}
\include
{
tex/Streaming
}
\include
{
tex/Testing
}
\part
{
I/O Library
}
\include
{
tex/NetworkTransparency
}
...
...
examples/CMakeLists.txt
View file @
962b23a4
...
...
@@ -55,6 +55,9 @@ add(remoting distributed_calculator)
add
(
broker simple_broker
)
add
(
broker simple_http_broker
)
# testing DSL
add
(
testing ping_pong
)
if
(
CAF_BUILD_PROTOBUF_EXAMPLES
)
find_package
(
Protobuf
)
if
(
PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE
)
...
...
examples/testing/ping_pong.cpp
0 → 100644
View file @
962b23a4
// Manual refs: lines 12-65 (Testing)
#define CAF_SUITE ping_pong
#include "caf/test/dsl.hpp"
#include "caf/test/unit_test_impl.hpp"
#include "caf/all.hpp"
using
namespace
caf
;
namespace
{
using
ping_atom
=
atom_constant
<
atom
(
"ping"
)
>
;
using
pong_atom
=
atom_constant
<
atom
(
"pong"
)
>
;
behavior
ping
(
event_based_actor
*
self
,
actor
pong_actor
,
int
n
)
{
self
->
send
(
pong_actor
,
ping_atom
::
value
,
n
);
return
{
[
=
](
pong_atom
,
int
x
)
{
if
(
x
>
1
)
self
->
send
(
pong_actor
,
ping_atom
::
value
,
x
-
1
);
}
};
}
behavior
pong
()
{
return
{
[
=
](
ping_atom
,
int
x
)
{
return
std
::
make_tuple
(
pong_atom
::
value
,
x
);
}
};
}
struct
ping_pong_fixture
:
test_coordinator_fixture
<>
{
actor
pong_actor
;
ping_pong_fixture
()
{
// Spawn the Pong actor.
pong_actor
=
sys
.
spawn
(
pong
);
// Run initialization code for Pong.
run
();
}
};
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
ping_pong_tests
,
ping_pong_fixture
)
CAF_TEST
(
three
pings
)
{
// Spawn the Ping actor and run its initialization code.
auto
ping_actor
=
sys
.
spawn
(
ping
,
pong_actor
,
3
);
sched
.
run_once
();
// Test communication between Ping and Pong.
expect
((
ping_atom
,
int
),
from
(
ping_actor
).
to
(
pong_actor
).
with
(
_
,
3
));
expect
((
pong_atom
,
int
),
from
(
pong_actor
).
to
(
ping_actor
).
with
(
_
,
3
));
expect
((
ping_atom
,
int
),
from
(
ping_actor
).
to
(
pong_actor
).
with
(
_
,
2
));
expect
((
pong_atom
,
int
),
from
(
pong_actor
).
to
(
ping_actor
).
with
(
_
,
2
));
expect
((
ping_atom
,
int
),
from
(
ping_actor
).
to
(
pong_actor
).
with
(
_
,
1
));
expect
((
pong_atom
,
int
),
from
(
pong_actor
).
to
(
ping_actor
).
with
(
_
,
1
));
// No further messages allowed.
disallow
((
ping_atom
,
int
),
from
(
ping_actor
).
to
(
pong_actor
).
with
(
_
,
1
));
}
CAF_TEST_FIXTURE_SCOPE_END
()
libcaf_core/caf/detail/type_traits.hpp
View file @
962b23a4
...
...
@@ -158,6 +158,13 @@ struct is_primitive {
||
std
::
is_convertible
<
T
,
atom_value
>::
value
;
};
// Workaround for weird GCC 4.8 STL implementation that breaks
// `std::is_convertible<T, atom_value>::value` for tuples containing atom
// constants.
// TODO: remove when dropping support for GCC 4.8.
template
<
class
...
Ts
>
struct
is_primitive
<
std
::
tuple
<
Ts
...
>>
:
std
::
false_type
{};
/// Checks whether `T1` is comparable with `T2`.
template
<
class
T1
,
class
T2
>
class
is_comparable
{
...
...
libcaf_test/caf/test/dsl.hpp
View file @
962b23a4
...
...
@@ -413,8 +413,10 @@ public:
template
<
class
...
Us
>
void
with
(
Us
&&
...
xs
)
{
// TODO: replace this workaround with make_tuple() when dropping support
// for GCC 4.8.
std
::
tuple
<
typename
std
::
decay
<
Us
>::
type
...
>
tmp
{
std
::
forward
<
Us
>
(
xs
)...};
// TODO: move tmp into lambda when switching to C++14
auto
tmp
=
std
::
make_tuple
(
std
::
forward
<
Us
>
(
xs
)...);
peek_
=
[
=
]
{
using
namespace
caf
::
detail
;
elementwise_compare_inspector
<
decltype
(
tmp
)
>
inspector
{
tmp
};
...
...
@@ -491,8 +493,10 @@ public:
template
<
class
...
Us
>
void
with
(
Us
&&
...
xs
)
{
// TODO: replace this workaround with make_tuple() when dropping support
// for GCC 4.8.
std
::
tuple
<
typename
std
::
decay
<
Us
>::
type
...
>
tmp
{
std
::
forward
<
Us
>
(
xs
)...};
// TODO: move tmp into lambda when switching to C++14
auto
tmp
=
std
::
make_tuple
(
std
::
forward
<
Us
>
(
xs
)...);
check_
=
[
=
]
{
auto
ptr
=
next_mailbox_element
(
dest_
);
if
(
ptr
==
nullptr
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment