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
2a43ea7d
Commit
2a43ea7d
authored
Aug 06, 2023
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement new fixture for deterministic testing
parent
6b59bb87
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1062 additions
and
0 deletions
+1062
-0
libcaf_core/caf/scheduled_actor.hpp
libcaf_core/caf/scheduled_actor.hpp
+5
-0
libcaf_test/CMakeLists.txt
libcaf_test/CMakeLists.txt
+2
-0
libcaf_test/caf/test/fixture/deterministic.cpp
libcaf_test/caf/test/fixture/deterministic.cpp
+309
-0
libcaf_test/caf/test/fixture/deterministic.hpp
libcaf_test/caf/test/fixture/deterministic.hpp
+408
-0
libcaf_test/caf/test/fixture/deterministic.test.cpp
libcaf_test/caf/test/fixture/deterministic.test.cpp
+338
-0
No files found.
libcaf_core/caf/scheduled_actor.hpp
View file @
2a43ea7d
...
@@ -241,6 +241,11 @@ public:
...
@@ -241,6 +241,11 @@ public:
return
*
mailbox_
;
return
*
mailbox_
;
}
}
/// Checks whether this actor is fully initialized.
bool
initialized
()
const
noexcept
{
return
getf
(
is_initialized_flag
);
}
// -- event handlers ---------------------------------------------------------
// -- event handlers ---------------------------------------------------------
/// Sets a custom handler for unexpected messages.
/// Sets a custom handler for unexpected messages.
...
...
libcaf_test/CMakeLists.txt
View file @
2a43ea7d
...
@@ -22,6 +22,8 @@ caf_add_component(
...
@@ -22,6 +22,8 @@ caf_add_component(
caf/test/block.cpp
caf/test/block.cpp
caf/test/context.cpp
caf/test/context.cpp
caf/test/factory.cpp
caf/test/factory.cpp
caf/test/fixture/deterministic.cpp
caf/test/fixture/deterministic.test.cpp
caf/test/given.cpp
caf/test/given.cpp
caf/test/nesting_error.cpp
caf/test/nesting_error.cpp
caf/test/registry.cpp
caf/test/registry.cpp
...
...
libcaf_test/caf/test/fixture/deterministic.cpp
0 → 100644
View file @
2a43ea7d
// 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/fixture/deterministic.hpp"
#include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/mailbox_factory.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/scheduled_actor.hpp"
namespace
caf
::
test
::
fixture
{
// -- mailbox ------------------------------------------------------------------
class
deterministic
::
mailbox_impl
:
public
ref_counted
,
public
abstract_mailbox
{
public:
mailbox_impl
(
deterministic
*
fix
,
scheduled_actor
*
owner
)
:
fix_
(
fix
),
owner_
(
owner
)
{
}
intrusive
::
inbox_result
push_back
(
mailbox_element_ptr
ptr
)
override
{
if
(
closed_
)
{
detail
::
sync_request_bouncer
bouncer
{
close_reason_
};
bouncer
(
*
ptr
);
return
intrusive
::
inbox_result
::
queue_closed
;
}
using
event_t
=
deterministic
::
scheduling_event
;
auto
unblocked
=
fix_
->
mail_count
(
owner_
)
==
0
;
auto
event
=
std
::
make_unique
<
event_t
>
(
owner_
,
std
::
move
(
ptr
));
fix_
->
events_
.
push_back
(
std
::
move
(
event
));
return
unblocked
?
intrusive
::
inbox_result
::
unblocked_reader
:
intrusive
::
inbox_result
::
success
;
}
void
push_front
(
mailbox_element_ptr
ptr
)
override
{
using
event_t
=
deterministic
::
scheduling_event
;
auto
event
=
std
::
make_unique
<
event_t
>
(
owner_
,
std
::
move
(
ptr
));
fix_
->
events_
.
emplace_front
(
std
::
move
(
event
));
}
mailbox_element_ptr
pop_front
()
override
{
return
fix_
->
pop_msg_impl
(
owner_
);
}
bool
closed
()
const
noexcept
override
{
return
closed_
;
}
bool
blocked
()
const
noexcept
override
{
return
blocked_
;
}
bool
try_block
()
override
{
blocked_
=
true
;
return
true
;
}
bool
try_unblock
()
override
{
if
(
!
blocked_
)
return
false
;
blocked_
=
false
;
return
true
;
}
size_t
close
(
const
error
&
reason
)
override
{
closed_
=
true
;
close_reason_
=
reason
;
auto
result
=
size_t
{
0
};
auto
envelope
=
fix_
->
pop_msg_impl
(
owner_
);
while
(
envelope
!=
nullptr
)
{
detail
::
sync_request_bouncer
bouncer
{
reason
};
++
result
;
envelope
=
fix_
->
pop_msg_impl
(
owner_
);
}
return
result
;
}
size_t
size
()
override
{
return
fix_
->
mail_count
(
owner_
);
}
void
ref_mailbox
()
noexcept
override
{
ref
();
}
void
deref_mailbox
()
noexcept
override
{
deref
();
}
mailbox_element
*
peek
(
message_id
)
override
{
// Note: this function only exists for backwards compatibility with the old
// unit testing framework. It is not used by the new test runner and thus
// not implemented.
CAF_RAISE_ERROR
(
std
::
logic_error
,
"peek not supported by this mailbox"
);
}
private:
bool
blocked_
=
false
;
bool
closed_
=
false
;
error
close_reason_
;
deterministic
*
fix_
;
scheduled_actor
*
owner_
;
};
class
deterministic
::
mailbox_factory_impl
:
public
detail
::
mailbox_factory
{
public:
explicit
mailbox_factory_impl
(
deterministic
*
fix
)
:
fix_
(
fix
)
{
// nop
}
abstract_mailbox
*
make
(
scheduled_actor
*
owner
)
override
{
return
new
deterministic
::
mailbox_impl
(
fix_
,
owner
);
}
abstract_mailbox
*
make
(
blocking_actor
*
)
override
{
return
nullptr
;
}
private:
deterministic
*
fix_
;
};
// -- scheduler ----------------------------------------------------------------
class
deterministic
::
scheduler_impl
:
public
scheduler
::
abstract_coordinator
{
public:
using
super
=
caf
::
scheduler
::
abstract_coordinator
;
scheduler_impl
(
actor_system
&
sys
,
deterministic
*
fix
)
:
super
(
sys
),
fix_
(
fix
)
{
// nop
}
bool
detaches_utility_actors
()
const
override
{
return
false
;
}
detail
::
test_actor_clock
&
clock
()
noexcept
override
{
return
clock_
;
}
protected:
void
start
()
override
{
// nop
}
void
stop
()
override
{
// nop
}
void
enqueue
(
resumable
*
ptr
)
override
{
using
event_t
=
deterministic
::
scheduling_event
;
using
subtype_t
=
resumable
::
subtype_t
;
switch
(
ptr
->
subtype
())
{
case
subtype_t
:
:
scheduled_actor
:
case
subtype_t
:
:
io_actor
:
{
// Actors put their messages into events_ directly. However, we do run
// them right away if they aren't initialized yet.
auto
dptr
=
static_cast
<
scheduled_actor
*>
(
ptr
);
if
(
!
dptr
->
initialized
())
dptr
->
resume
(
system_
.
dummy_execution_unit
(),
0
);
break
;
}
default:
fix_
->
events_
.
push_back
(
std
::
make_unique
<
event_t
>
(
ptr
,
nullptr
));
}
// Before calling this function, CAF *always* bumps the reference count.
// Hence, we need to release one reference count here.
intrusive_ptr_release
(
ptr
);
}
private:
/// The fixture this scheduler belongs to.
deterministic
*
fix_
;
/// Allows users to fake time at will.
detail
::
test_actor_clock
clock_
;
};
// -- config -------------------------------------------------------------------
deterministic
::
config
::
config
(
deterministic
*
fix
)
{
factory_
=
std
::
make_unique
<
mailbox_factory_impl
>
(
fix
);
module_factories
.
push_back
([
fix
](
actor_system
&
sys
)
->
actor_system
::
module
*
{
return
new
scheduler_impl
(
sys
,
fix
);
});
}
deterministic
::
config
::~
config
()
{
// nop
}
detail
::
mailbox_factory
*
deterministic
::
config
::
mailbox_factory
()
{
return
factory_
.
get
();
}
// -- abstract_message_predicate -----------------------------------------------
deterministic
::
abstract_message_predicate
::~
abstract_message_predicate
()
{
// nop
}
// -- fixture ------------------------------------------------------------------
deterministic
::
deterministic
()
:
cfg
(
this
),
sys
(
cfg
)
{
// nop
}
deterministic
::~
deterministic
()
{
events_
.
clear
();
}
bool
deterministic
::
prepone_event_impl
(
const
strong_actor_ptr
&
receiver
)
{
actor_predicate
any_sender
{
std
::
ignore
};
message_predicate
any_payload
{
std
::
ignore
};
return
prepone_event_impl
(
receiver
,
any_sender
,
any_payload
);
}
bool
deterministic
::
prepone_event_impl
(
const
strong_actor_ptr
&
receiver
,
actor_predicate
&
sender_pred
,
abstract_message_predicate
&
payload_pred
)
{
if
(
events_
.
empty
()
||
!
receiver
)
return
false
;
auto
first
=
events_
.
begin
();
auto
last
=
events_
.
end
();
auto
i
=
std
::
find_if
(
first
,
last
,
[
&
](
const
auto
&
event
)
{
auto
self
=
actor_cast
<
abstract_actor
*>
(
receiver
);
return
event
->
target
==
static_cast
<
scheduled_actor
*>
(
self
)
&&
sender_pred
(
event
->
item
->
sender
)
&&
payload_pred
(
event
->
item
->
payload
);
});
if
(
i
==
last
)
return
false
;
if
(
i
!=
first
)
{
auto
ptr
=
std
::
move
(
*
i
);
events_
.
erase
(
i
);
events_
.
insert
(
events_
.
begin
(),
std
::
move
(
ptr
));
}
return
true
;
}
deterministic
::
scheduling_event
*
deterministic
::
find_event_impl
(
const
strong_actor_ptr
&
receiver
)
{
if
(
events_
.
empty
()
||
!
receiver
)
return
nullptr
;
auto
last
=
events_
.
end
();
auto
i
=
std
::
find_if
(
events_
.
begin
(),
last
,
[
&
](
const
auto
&
event
)
{
auto
raw_ptr
=
actor_cast
<
abstract_actor
*>
(
receiver
);
return
event
->
target
==
static_cast
<
scheduled_actor
*>
(
raw_ptr
);
});
if
(
i
!=
last
)
return
i
->
get
();
return
nullptr
;
}
mailbox_element_ptr
deterministic
::
pop_msg_impl
(
scheduled_actor
*
receiver
)
{
auto
pred
=
[
&
](
const
auto
&
event
)
{
return
event
->
target
==
receiver
;
};
auto
first
=
events_
.
begin
();
auto
last
=
events_
.
end
();
auto
i
=
std
::
find_if
(
first
,
last
,
pred
);
if
(
i
==
last
)
return
nullptr
;
auto
result
=
std
::
move
((
*
i
)
->
item
);
events_
.
erase
(
i
);
return
result
;
}
size_t
deterministic
::
mail_count
(
scheduled_actor
*
receiver
)
{
if
(
receiver
==
nullptr
)
return
0
;
auto
pred
=
[
&
](
const
auto
&
event
)
{
return
event
->
target
==
receiver
;
};
return
std
::
count_if
(
events_
.
begin
(),
events_
.
end
(),
pred
);
}
size_t
deterministic
::
mail_count
(
const
strong_actor_ptr
&
receiver
)
{
auto
raw_ptr
=
actor_cast
<
abstract_actor
*>
(
receiver
);
return
mail_count
(
dynamic_cast
<
scheduled_actor
*>
(
raw_ptr
));
}
bool
deterministic
::
dispatch_message
()
{
if
(
events_
.
empty
())
return
false
;
if
(
events_
.
front
()
->
item
==
nullptr
)
{
// Regular resumable.
auto
ev
=
std
::
move
(
events_
.
front
());
events_
.
pop_front
();
ev
->
target
->
resume
(
sys
.
dummy_execution_unit
(),
0
);
return
true
;
}
// Actor: we simply resume the next actor and it will pick up its message.
auto
next
=
events_
.
front
()
->
target
;
next
->
resume
(
sys
.
dummy_execution_unit
(),
1
);
return
true
;
}
size_t
deterministic
::
dispatch_messages
()
{
size_t
result
=
0
;
while
(
dispatch_message
())
++
result
;
return
result
;
}
}
// namespace caf::test
libcaf_test/caf/test/fixture/deterministic.hpp
0 → 100644
View file @
2a43ea7d
// 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/runnable.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_actor_clock.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/scheduler/abstract_coordinator.hpp"
#include <list>
#include <memory>
namespace
caf
::
test
::
fixture
{
/// A fixture for writing unit tests that require deterministic scheduling. The
/// fixture equips tests with an actor system that uses a deterministic
/// scheduler and provides a DSL for writing high-level tests for message
/// passing between actors.
class
CAF_TEST_EXPORT
deterministic
{
private:
// -- private member types (implementation details) --------------------------
class
mailbox_impl
;
class
scheduler_impl
;
class
mailbox_factory_impl
;
/// Wraps a resumable pointer and a mailbox element pointer.
struct
scheduling_event
{
scheduling_event
(
resumable
*
target
,
mailbox_element_ptr
payload
)
:
target
(
target
),
item
(
std
::
move
(
payload
))
{
// nop
}
/// The target of the event.
intrusive_ptr
<
resumable
>
target
;
/// The message for the event or `nullptr` if the target is not an actor.
mailbox_element_ptr
item
;
};
/// A predicate for checking of single value. When constructing from a
/// reference wrapper, the predicate assigns the found value to the reference
/// instead of checking it.
template
<
class
T
>
class
value_predicate
{
public:
value_predicate
()
{
predicate_
=
[](
const
T
&
)
{
return
true
;
};
}
explicit
value_predicate
(
decltype
(
std
::
ignore
))
:
value_predicate
()
{
// nop
}
template
<
class
U
>
explicit
value_predicate
(
U
value
,
std
::
enable_if_t
<
detail
::
is_comparable_v
<
T
,
U
>>*
=
nullptr
)
{
predicate_
=
[
value
](
const
T
&
found
)
{
return
found
==
value
;
};
}
explicit
value_predicate
(
std
::
reference_wrapper
<
T
>
ref
)
{
predicate_
=
[
ref
](
const
T
&
found
)
{
ref
.
get
()
=
found
;
return
true
;
};
}
template
<
class
Predicate
,
class
=
std
::
enable_if_t
<
std
::
is_same_v
<
bool
,
decltype
(
std
::
declval
<
Predicate
>()(
std
::
declval
<
const
T
&>
()))
>>>
explicit
value_predicate
(
Predicate
predicate
)
{
predicate_
=
std
::
move
(
predicate
);
}
value_predicate
(
value_predicate
&&
)
=
default
;
value_predicate
(
const
value_predicate
&
)
=
default
;
value_predicate
&
operator
=
(
value_predicate
&&
)
=
default
;
value_predicate
&
operator
=
(
const
value_predicate
&
)
=
default
;
bool
operator
()(
const
T
&
value
)
{
return
predicate_
(
value
);
}
private:
std
::
function
<
bool
(
const
T
&
)
>
predicate_
;
};
/// Convenience alias for predicates on strong actor pointers.
using
actor_predicate
=
value_predicate
<
strong_actor_ptr
>
;
/// Abstract base type for message predicates.
class
abstract_message_predicate
{
public:
virtual
~
abstract_message_predicate
();
virtual
bool
operator
()(
const
message
&
)
=
0
;
};
/// A predicate for checking type and (optionally) content of a message.
template
<
class
...
Ts
>
class
message_predicate
:
public
abstract_message_predicate
{
public:
template
<
class
U
,
class
...
Us
>
explicit
message_predicate
(
U
x
,
Us
...
xs
)
{
predicates_
=
std
::
make_shared
<
predicates_tuple
>
(
std
::
move
(
x
),
std
::
move
(
xs
)...);
}
explicit
message_predicate
(
decltype
(
std
::
ignore
))
{
// nop
}
message_predicate
()
{
predicates_
=
std
::
make_shared
<
predicates_tuple
>
();
}
message_predicate
(
message_predicate
&&
)
=
default
;
message_predicate
(
const
message_predicate
&
)
=
default
;
message_predicate
&
operator
=
(
message_predicate
&&
)
=
default
;
message_predicate
&
operator
=
(
const
message_predicate
&
)
=
default
;
bool
operator
()(
const
message
&
msg
)
{
if
(
!
predicates_
)
return
true
;
if
(
auto
view
=
make_const_typed_message_view
<
Ts
...
>
(
msg
))
return
do_check
(
view
,
std
::
index_sequence_for
<
Ts
...
>
{});
return
false
;
}
private:
template
<
size_t
...
Is
>
bool
do_check
(
const_typed_message_view
<
Ts
...
>
view
,
//
std
::
index_sequence
<
Is
...
>
)
{
return
(((
std
::
get
<
Is
>
(
*
predicates_
))(
get
<
Is
>
(
view
)))
&&
...);
}
using
predicates_tuple
=
std
::
tuple
<
value_predicate
<
Ts
>
...
>
;
std
::
shared_ptr
<
predicates_tuple
>
predicates_
;
};
public:
// -- public member types ----------------------------------------------------
/// The configuration type for this fixture.
class
config
:
public
actor_system_config
{
public:
config
(
deterministic
*
fix
);
~
config
()
override
;
private:
detail
::
mailbox_factory
*
mailbox_factory
()
override
;
std
::
unique_ptr
<
detail
::
mailbox_factory
>
factory_
;
};
/// Configures the algorithm to evaluate for an `evaluator` instances.
enum
class
evaluator_algorithm
{
expect
,
allow
,
prepone
,
prepone_and_expect
,
prepone_and_allow
};
/// Provides a fluent interface for matching messages. The `evaluator` allows
/// setting `from` and `with` parameters for an algorithm that matches
/// messages against a predicate. When setting the only mandatory parameter
/// `to`, the `evaluator` evaluates the predicate against the next message
/// in the mailbox of the target actor.
template
<
class
...
Ts
>
class
evaluator
{
public:
evaluator
(
deterministic
*
fix
,
detail
::
source_location
loc
,
evaluator_algorithm
algorithm
)
:
fix_
(
fix
),
loc_
(
loc
),
algo_
(
algorithm
)
{
// nop
}
evaluator
()
=
delete
;
evaluator
(
evaluator
&&
)
noexcept
=
default
;
evaluator
&
operator
=
(
evaluator
&&
)
noexcept
=
default
;
evaluator
(
const
evaluator
&
)
=
delete
;
evaluator
&
operator
=
(
const
evaluator
&
)
=
delete
;
template
<
class
...
Us
>
evaluator
&&
with
(
Us
...
xs
)
&&
{
with_
=
message_predicate
<
Ts
...
>
(
std
::
move
(
xs
)...);
return
std
::
move
(
*
this
);
}
evaluator
&&
from
(
const
strong_actor_ptr
&
src
)
&&
{
from_
=
value_predicate
<
strong_actor_ptr
>
{
std
::
move
(
src
)};
return
std
::
move
(
*
this
);
}
evaluator
&&
from
(
std
::
nullptr_t
)
&&
{
return
std
::
move
(
*
this
).
from
(
strong_actor_ptr
{});
}
evaluator
&&
from
(
const
actor
&
src
)
&&
{
from_
=
value_predicate
<
strong_actor_ptr
>
{
std
::
move
(
src
)};
return
std
::
move
(
*
this
);
}
template
<
class
...
Us
>
evaluator
&&
from
(
const
typed_actor
<
Us
...
>&
src
)
&&
{
from_
=
value_predicate
<
strong_actor_ptr
>
{
std
::
move
(
src
)};
return
std
::
move
(
*
this
);
}
evaluator
&&
from
(
std
::
reference_wrapper
<
strong_actor_ptr
>
src
)
&&
{
from_
=
value_predicate
<
strong_actor_ptr
>
{
src
};
return
std
::
move
(
*
this
);
}
template
<
class
T
>
bool
to
(
const
T
&
dst
)
&&
{
auto
dst_ptr
=
actor_cast
<
strong_actor_ptr
>
(
dst
);
switch
(
algo_
)
{
case
evaluator_algorithm
:
:
expect
:
return
eval_dispatch
(
dst_ptr
,
true
);
case
evaluator_algorithm
:
:
allow
:
return
eval_dispatch
(
dst_ptr
,
false
);
case
evaluator_algorithm
:
:
prepone
:
return
eval_prepone
(
dst_ptr
);
case
evaluator_algorithm
:
:
prepone_and_expect
:
eval_prepone
(
dst_ptr
);
return
eval_dispatch
(
dst_ptr
,
true
);
case
evaluator_algorithm
:
:
prepone_and_allow
:
return
eval_prepone
(
dst_ptr
)
&&
eval_dispatch
(
dst_ptr
,
false
);
default:
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid algorithm"
);
}
}
private:
using
predicates
=
std
::
tuple
<
value_predicate
<
Ts
>
...
>
;
bool
eval_dispatch
(
const
strong_actor_ptr
&
dst
,
bool
fail_on_mismatch
)
{
auto
&
ctx
=
runnable
::
current
();
auto
*
event
=
fix_
->
find_event_impl
(
dst
);
if
(
!
event
)
{
if
(
fail_on_mismatch
)
ctx
.
fail
({
"no matching message found"
,
loc_
});
return
false
;
}
if
(
!
from_
(
event
->
item
->
sender
)
||
!
with_
(
event
->
item
->
payload
))
{
if
(
fail_on_mismatch
)
ctx
.
fail
({
"no matching message found"
,
loc_
});
return
false
;
}
fix_
->
prepone_event_impl
(
dst
);
if
(
fail_on_mismatch
)
{
if
(
!
fix_
->
dispatch_message
())
ctx
.
fail
({
"failed to dispatch message"
,
loc_
});
reporter
::
instance
->
pass
(
loc_
);
return
true
;
}
return
fix_
->
dispatch_message
();
}
bool
eval_prepone
(
const
strong_actor_ptr
&
dst
)
{
return
fix_
->
prepone_event_impl
(
dst
,
from_
,
with_
);
}
deterministic
*
fix_
;
detail
::
source_location
loc_
;
evaluator_algorithm
algo_
;
actor_predicate
from_
;
message_predicate
<
Ts
...
>
with_
;
};
// -- friends ----------------------------------------------------------------
friend
class
mailbox_impl
;
friend
class
scheduler_impl
;
template
<
class
...
Ts
>
friend
class
evaluator
;
// -- constructors, destructors, and assignment operators --------------------
deterministic
();
~
deterministic
();
// -- properties -------------------------------------------------------------
/// Returns the number of pending messages for `receiver`.
size_t
mail_count
(
scheduled_actor
*
receiver
);
/// Returns the number of pending messages for `receiver`.
size_t
mail_count
(
const
strong_actor_ptr
&
receiver
);
/// Returns the number of pending messages for `receiver`.
size_t
mail_count
(
const
actor
&
receiver
)
{
return
mail_count
(
actor_cast
<
strong_actor_ptr
>
(
receiver
));
}
/// Returns the number of pending messages for `receiver`.
template
<
class
...
Ts
>
size_t
mail_count
(
const
typed_actor
<
Ts
...
>&
receiver
)
{
return
mail_count
(
actor_cast
<
strong_actor_ptr
>
(
receiver
));
}
// -- control flow -----------------------------------------------------------
/// Tries to dispatch a single message.
bool
dispatch_message
();
/// Dispatches all pending messages.
size_t
dispatch_messages
();
// -- message-based predicates -----------------------------------------------
/// Expects a message with types `Ts...` as the next message in the mailbox of
/// the receiver and aborts the test if the message is missing. Otherwise
/// executes the message.
template
<
class
...
Ts
>
auto
expect
(
const
detail
::
source_location
&
loc
=
detail
::
source_location
::
current
())
{
return
evaluator
<
Ts
...
>
{
this
,
loc
,
evaluator_algorithm
::
expect
};
}
/// Tries to match a message with types `Ts...` and executes it if it is the
/// next message in the mailbox of the receiver.
template
<
class
...
Ts
>
auto
allow
(
const
detail
::
source_location
&
loc
=
detail
::
source_location
::
current
())
{
return
evaluator
<
Ts
...
>
{
this
,
loc
,
evaluator_algorithm
::
allow
};
}
/// Tries to prepone a message, i.e., reorders the messages in the mailbox of
/// the receiver such that the next call to `dispatch_message` will run it.
/// @returns `true` if the message could be preponed, `false` otherwise.
template
<
class
...
Ts
>
auto
prepone
(
const
detail
::
source_location
&
loc
=
detail
::
source_location
::
current
())
{
return
evaluator
<
Ts
...
>
{
this
,
loc
,
evaluator_algorithm
::
prepone
};
}
/// Shortcut for calling `prepone` and then `expect` with the same arguments.
template
<
class
...
Ts
>
auto
prepone_and_expect
(
const
detail
::
source_location
&
loc
=
detail
::
source_location
::
current
())
{
return
evaluator
<
Ts
...
>
{
this
,
loc
,
evaluator_algorithm
::
prepone_and_expect
};
}
/// Shortcut for calling `prepone` and then `allow` with the same arguments.
template
<
class
...
Ts
>
auto
prepone_and_allow
(
const
detail
::
source_location
&
loc
=
detail
::
source_location
::
current
())
{
return
evaluator
<
Ts
...
>
{
this
,
loc
,
evaluator_algorithm
::
prepone_and_allow
};
}
// -- member variables -------------------------------------------------------
/// Configures the actor system with deterministic scheduling.
config
cfg
;
/// The actor system instance for the tests.
actor_system
sys
;
private:
/// Tries find a message in `events_` that matches the given predicate and
/// moves it to the front of the queue.
bool
prepone_event_impl
(
const
strong_actor_ptr
&
receiver
);
/// Tries find a message in `events_` that matches the given predicates and
/// moves it to the front of the queue.
bool
prepone_event_impl
(
const
strong_actor_ptr
&
receiver
,
actor_predicate
&
sender_pred
,
abstract_message_predicate
&
payload_pred
);
/// Returns the next event for `receiver` or `nullptr` if there is none.
scheduling_event
*
find_event_impl
(
const
strong_actor_ptr
&
receiver
);
/// Removes the next message for `receiver` from the queue and returns it.
mailbox_element_ptr
pop_msg_impl
(
scheduled_actor
*
receiver
);
/// Stores all pending messages of scheduled actors.
std
::
list
<
std
::
unique_ptr
<
scheduling_event
>>
events_
;
};
}
// namespace caf::test
libcaf_test/caf/test/fixture/deterministic.test.cpp
0 → 100644
View file @
2a43ea7d
// 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/fixture/deterministic.hpp"
#include "caf/test/caf_test_main.hpp"
#include "caf/test/test.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/send.hpp"
namespace
fixture
=
caf
::
test
::
fixture
;
template
<
class
T
>
using
value_predicate
=
fixture
::
deterministic
::
value_predicate
<
T
>
;
template
<
class
...
Ts
>
using
message_predicate
=
fixture
::
deterministic
::
message_predicate
<
Ts
...
>
;
struct
my_int
{
int
value
;
};
bool
operator
==
(
my_int
lhs
,
int
rhs
)
noexcept
{
return
lhs
.
value
==
rhs
;
}
bool
operator
==
(
int
lhs
,
my_int
rhs
)
noexcept
{
return
lhs
==
rhs
.
value
;
}
TEST
(
"value predicates check or extract individual values"
)
{
using
predicate_t
=
value_predicate
<
int
>
;
SECTION
(
"catch-all predicates are constructible from std::ignore"
)
{
predicate_t
f
{
std
::
ignore
};
check
(
f
(
1
));
check
(
f
(
2
));
check
(
f
(
3
));
}
SECTION
(
"a default-constructed predicate always returns true"
)
{
predicate_t
f
;
check
(
f
(
1
));
check
(
f
(
2
));
check
(
f
(
3
));
}
SECTION
(
"exact match predicates are constructible from a value"
)
{
predicate_t
f
{
2
};
check
(
!
f
(
1
));
check
(
f
(
2
));
check
(
!
f
(
3
));
}
SECTION
(
"exact match predicates are constructible from any comparable type"
)
{
predicate_t
f
{
my_int
{
2
}};
check
(
!
f
(
1
));
check
(
f
(
2
));
check
(
!
f
(
3
));
}
SECTION
(
"custom predicates are constructible from a function object"
)
{
predicate_t
f
{[](
int
x
)
{
return
x
<=
2
;
}};
check
(
f
(
1
));
check
(
f
(
2
));
check
(
!
f
(
3
));
}
SECTION
(
"extractors are constructible from a reference wrapper"
)
{
int
x
=
0
;
predicate_t
f
{
std
::
ref
(
x
)};
check
(
f
(
1
))
&&
check_eq
(
x
,
1
);
check
(
f
(
2
))
&&
check_eq
(
x
,
2
);
check
(
f
(
3
))
&&
check_eq
(
x
,
3
);
}
}
TEST
(
"message predicates check all values in a message"
)
{
using
predicate_t
=
message_predicate
<
int
,
std
::
string
,
double
>
;
SECTION
(
"a default-constructed message predicate matches anything"
)
{
predicate_t
f
;
check
(
f
(
caf
::
make_message
(
1
,
"two"
,
3.0
)));
check
(
f
(
caf
::
make_message
(
42
,
"hello world"
,
7.7
)));
}
SECTION
(
"message predicates can match values"
)
{
predicate_t
f
{
1
,
"two"
,
[](
double
x
)
{
return
x
<
5.0
;
}};
check
(
f
(
caf
::
make_message
(
1
,
"two"
,
3.0
)));
check
(
!
f
(
caf
::
make_message
(
1
,
"two"
,
6.0
)));
}
SECTION
(
"message predicates can extract values"
)
{
auto
x0
=
0
;
auto
x1
=
std
::
string
{};
auto
x2
=
0.0
;
predicate_t
f
{
std
::
ref
(
x0
),
std
::
ref
(
x1
),
std
::
ref
(
x2
)};
check
(
f
(
caf
::
make_message
(
1
,
"two"
,
3.0
)));
check_eq
(
x0
,
1
);
check_eq
(
x1
,
"two"
);
check_eq
(
x2
,
3.0
);
}
}
WITH_FIXTURE
(
fixture
::
deterministic
)
{
TEST
(
"the deterministic fixture provides a deterministic scheduler"
)
{
auto
initialized
=
std
::
make_shared
<
bool
>
(
false
);
auto
count
=
std
::
make_shared
<
int32_t
>
(
0
);
auto
worker
=
sys
.
spawn
([
initialized
,
count
]
{
*
initialized
=
true
;
return
caf
::
behavior
{
[
count
](
int32_t
value
)
{
*
count
+=
value
;
},
[
count
](
const
std
::
string
&
str
)
{
if
(
auto
ival
=
caf
::
get_as
<
int32_t
>
(
caf
::
config_value
{
str
}))
*
count
+=
*
ival
;
},
};
});
caf
::
scoped_actor
self
{
sys
};
check
(
*
initialized
);
check_eq
(
mail_count
(
worker
),
0u
);
anon_send
(
worker
,
1
);
check_eq
(
mail_count
(
worker
),
1u
);
self
->
send
(
worker
,
2
);
check_eq
(
mail_count
(
worker
),
2u
);
anon_send
(
worker
,
3
);
check_eq
(
mail_count
(
worker
),
3u
);
SECTION
(
"calling dispatch_message processes a single message"
)
{
check
(
dispatch_message
());
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
1
);
check
(
dispatch_message
());
check_eq
(
mail_count
(
worker
),
1u
);
check_eq
(
*
count
,
3
);
check
(
dispatch_message
());
check_eq
(
mail_count
(
worker
),
0u
);
check_eq
(
*
count
,
6
);
check
(
!
dispatch_message
());
}
SECTION
(
"calling dispatch_messages processes all messages"
)
{
check_eq
(
dispatch_messages
(),
3u
);
check_eq
(
*
count
,
6
);
}
#ifdef CAF_ENABLE_EXCEPTIONS
SECTION
(
"expect() checks for required messages"
)
{
expect
<
int32_t
>
().
to
(
worker
);
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
1
);
expect
<
int32_t
>
().
to
(
worker
);
check_eq
(
mail_count
(
worker
),
1u
);
check_eq
(
*
count
,
3
);
expect
<
int32_t
>
().
to
(
worker
);
check_eq
(
mail_count
(
worker
),
0u
);
check_eq
(
*
count
,
6
);
should_fail_with_exception
([
this
,
worker
]
{
//
expect
<
int32_t
>
().
with
(
3
).
to
(
worker
);
});
}
SECTION
(
"expect() matches the types of the next message"
)
{
anon_send
(
worker
,
"4"
);
should_fail_with_exception
([
this
,
worker
]
{
//
expect
<
std
::
string
>
().
to
(
worker
);
});
should_fail_with_exception
([
this
,
worker
]
{
//
expect
<
int32_t
,
int32_t
>
().
to
(
worker
);
});
check_eq
(
*
count
,
0
);
check_eq
(
dispatch_messages
(),
4u
);
check_eq
(
*
count
,
10
);
}
SECTION
(
"expect() optionally matches the content of the next message"
)
{
should_fail_with_exception
([
this
,
worker
]
{
//
expect
<
int32_t
>
().
with
(
3
).
to
(
worker
);
});
check_eq
(
*
count
,
0
);
expect
<
int32_t
>
().
with
(
1
).
to
(
worker
);
expect
<
int32_t
>
().
with
(
2
).
to
(
worker
);
expect
<
int32_t
>
().
with
(
3
).
to
(
worker
);
check_eq
(
*
count
,
6
);
}
SECTION
(
"expect() optionally matches the sender of the next message"
)
{
// First message has no sender.
should_fail_with_exception
([
this
,
worker
,
&
self
]
{
//
expect
<
int32_t
>
().
from
(
self
).
to
(
worker
);
});
check_eq
(
*
count
,
0
);
expect
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
);
check_eq
(
*
count
,
1
);
// Second message is from self.
should_fail_with_exception
([
this
,
worker
]
{
//
expect
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
);
});
check_eq
(
*
count
,
1
);
expect
<
int32_t
>
().
from
(
self
).
to
(
worker
);
check_eq
(
*
count
,
3
);
}
SECTION
(
"prepone_and_expect() processes out of order based on types"
)
{
anon_send
(
worker
,
"4"
);
prepone_and_expect
<
std
::
string
>
().
to
(
worker
);
check_eq
(
*
count
,
4
);
should_fail_with_exception
([
this
,
worker
]
{
//
prepone_and_expect
<
std
::
string
>
().
to
(
worker
);
});
check_eq
(
*
count
,
4
);
check_eq
(
dispatch_messages
(),
3u
);
check_eq
(
*
count
,
10
);
}
SECTION
(
"prepone_and_expect() processes out of order based on content"
)
{
prepone_and_expect
<
int32_t
>
().
with
(
3
).
to
(
worker
);
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
3
);
should_fail_with_exception
([
this
,
worker
]
{
//
prepone_and_expect
<
int32_t
>
().
with
(
3
).
to
(
worker
);
});
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
3
);
prepone_and_expect
<
int32_t
>
().
with
(
1
).
to
(
worker
);
check_eq
(
*
count
,
4
);
prepone_and_expect
<
int32_t
>
().
with
(
2
).
to
(
worker
);
check_eq
(
*
count
,
6
);
check
(
!
dispatch_message
());
}
SECTION
(
"prepone_and_expect() processes out of order based on senders"
)
{
prepone_and_expect
<
int32_t
>
().
from
(
self
).
to
(
worker
);
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
2
);
should_fail_with_exception
([
this
,
worker
,
&
self
]
{
//
prepone_and_expect
<
int32_t
>
().
from
(
self
).
to
(
worker
);
});
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
2
);
prepone_and_expect
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
);
check_eq
(
*
count
,
3
);
prepone_and_expect
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
);
check_eq
(
*
count
,
6
);
check
(
!
dispatch_message
());
}
#endif // CAF_ENABLE_EXCEPTIONS
SECTION
(
"allow() checks for optional messages"
)
{
check
(
allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
1
);
check
(
allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
1u
);
check_eq
(
*
count
,
3
);
check
(
allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
0u
);
check_eq
(
*
count
,
6
);
check
(
!
allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
}
SECTION
(
"allow() matches the types of the next message"
)
{
anon_send
(
worker
,
"4"
);
check
(
!
allow
<
std
::
string
>
().
to
(
worker
));
check
(
!
allow
<
int32_t
,
int32_t
>
().
to
(
worker
));
check_eq
(
*
count
,
0
);
check_eq
(
dispatch_messages
(),
4u
);
check_eq
(
*
count
,
10
);
}
SECTION
(
"allow() optionally matches the content of the next message"
)
{
check
(
!
allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
check_eq
(
*
count
,
0
);
check
(
allow
<
int32_t
>
().
with
(
1
).
to
(
worker
));
check
(
allow
<
int32_t
>
().
with
(
2
).
to
(
worker
));
check
(
allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
check_eq
(
*
count
,
6
);
}
SECTION
(
"allow() optionally matches the sender of the next message"
)
{
// First message has no sender.
check
(
!
allow
<
int32_t
>
().
from
(
self
).
to
(
worker
));
check_eq
(
*
count
,
0
);
check
(
allow
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
));
check_eq
(
*
count
,
1
);
// Second message is from self.
check
(
!
allow
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
));
check_eq
(
*
count
,
1
);
check
(
allow
<
int32_t
>
().
from
(
self
).
to
(
worker
));
check_eq
(
*
count
,
3
);
}
SECTION
(
"prepone_and_allow() checks for optional messages"
)
{
check
(
prepone_and_allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
1
);
check
(
prepone_and_allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
1u
);
check_eq
(
*
count
,
3
);
check
(
prepone_and_allow
<
int32_t
>
().
to
(
worker
));
check_eq
(
mail_count
(
worker
),
0u
);
check_eq
(
*
count
,
6
);
check
(
!
prepone_and_allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
}
SECTION
(
"prepone_and_allow() processes out of order based on types"
)
{
anon_send
(
worker
,
"4"
);
check
(
prepone_and_allow
<
std
::
string
>
().
to
(
worker
));
check
(
!
prepone_and_allow
<
std
::
string
>
().
to
(
worker
));
check_eq
(
*
count
,
4
);
check_eq
(
dispatch_messages
(),
3u
);
check_eq
(
*
count
,
10
);
}
SECTION
(
"prepone_and_allow() processes out of order based on content"
)
{
check
(
prepone_and_allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
3
);
check
(
!
prepone_and_allow
<
int32_t
>
().
with
(
3
).
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
3
);
check
(
prepone_and_allow
<
int32_t
>
().
with
(
1
).
to
(
worker
));
check_eq
(
*
count
,
4
);
check
(
prepone_and_allow
<
int32_t
>
().
with
(
2
).
to
(
worker
));
check_eq
(
*
count
,
6
);
check
(
!
dispatch_message
());
}
SECTION
(
"prepone_and_allow() processes out of order based on senders"
)
{
check
(
prepone_and_allow
<
int32_t
>
().
from
(
self
).
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
2
);
check
(
!
prepone_and_allow
<
int32_t
>
().
from
(
self
).
to
(
worker
));
check_eq
(
mail_count
(
worker
),
2u
);
check_eq
(
*
count
,
2
);
check
(
prepone_and_allow
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
));
check_eq
(
*
count
,
3
);
check
(
prepone_and_allow
<
int32_t
>
().
from
(
nullptr
).
to
(
worker
));
check_eq
(
*
count
,
6
);
check
(
!
dispatch_message
());
}
SECTION
(
"prepone_and_allow() ignores non-existent combinations"
)
{
// There is no message with content (4).
check
(
!
prepone_and_allow
<
int32_t
>
().
with
(
4
).
from
(
self
).
to
(
worker
));
// There is no message with content (3) from self.
check
(
!
prepone_and_allow
<
int32_t
>
().
with
(
3
).
from
(
self
).
to
(
worker
));
// The original order should be preserved.
check
(
dispatch_message
());
check_eq
(
*
count
,
1
);
check
(
dispatch_message
());
check_eq
(
*
count
,
3
);
check
(
dispatch_message
());
check_eq
(
*
count
,
6
);
}
}
}
// WITH_FIXTURE(fixture::deterministic)
CAF_TEST_MAIN
()
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