1 /*
2 * SObjectizer-5
3 */
4
5 /*!
6 * \file
7 * \brief Testing related stuff.
8 *
9 * \since
10 * v.5.5.24
11 */
12
13 #pragma once
14
15 #include <so_5/all.hpp>
16
17 #include <sstream>
18
19 #if defined( SO_5_MSVC )
20 #pragma warning(push)
21 #pragma warning(disable: 4251)
22 #endif
23
24 namespace so_5 {
25
26 namespace experimental {
27
28 namespace testing {
29
30 inline namespace v1 {
31
32 namespace details {
33
34 //
35 // Forward declarations
36 //
37 class abstract_scenario_step_t;
38 class abstract_scenario_t;
39 class scenario_in_progress_accessor_t;
40
41 /*!
42 * \brief A description of an event for testing scenario.
43 *
44 * Instances of that type will be passed to various hooks of
45 * testing scenario and scenario's steps.
46 *
47 * \since
48 * v.5.5.24
49 */
50 struct incident_info_t final
51 {
52 //! Target of an event.
53 const agent_t * m_agent;
54 //! Type of message or signal.
55 const std::type_index m_msg_type;
56 //! ID of mbox from that message/signal was received.
57 mbox_id_t m_src_mbox_id;
58
incident_info_tso_5::experimental::testing::v1::details::incident_info_t59 incident_info_t(
60 const agent_t * agent,
61 const std::type_index & msg_type,
62 mbox_id_t src_mbox_id )
63 : m_agent( agent )
64 , m_msg_type( msg_type )
65 , m_src_mbox_id( src_mbox_id )
66 {}
67 };
68
69 /*!
70 * \brief What happened with source of an event.
71 *
72 * When a message or signal is delivered to an agent that message/signal
73 * can be either handled or ignored. Some scenario triggers are activated
74 * when source message/signal is handled, some are activated when incident
75 * is ignored. This enumeration can be used for selection of that cases.
76 *
77 * \since
78 * v.5.5.24
79 */
80 enum class incident_status_t
81 {
82 //! Message or signal has been handled.
83 handled,
84 //! Message or signal has been ignored.
85 ignored
86 };
87
88 /*!
89 * \brief Description of context on that a trigger is completed.
90 *
91 * Some triggers should do some actions on completion. Because
92 * of that triggers should have access to scenario's step or even to
93 * the whole scenario. This type holds a reference to objects those
94 * can be accessible to a trigger.
95 *
96 * \note
97 * The references inside that object are valid only for small amount
98 * of time. They shouldn't be used after completion of trigger.
99 *
100 * \since
101 * v.5.5.24
102 */
103 struct trigger_completion_context_t
104 {
105 const scenario_in_progress_accessor_t & m_scenario_accessor;
106 abstract_scenario_step_t & m_step;
107 };
108
109 /*!
110 * \brief An implementation of trigger for scenario's step.
111 *
112 * In the current version trigger is implemented as a concrete class (just for
113 * simplicity), but in the future versions it could be an abstract interface.
114 *
115 * \since
116 * v.5.5.24
117 */
118 class SO_5_TYPE trigger_t final
119 {
120 public :
121 using completion_function_t = std::function<
122 void(const trigger_completion_context_t &) /*noexcept*/ >;
123
124 private :
125 //! What should happen with initial message/signal.
126 const incident_status_t m_incident_status;
127 //! A reference to the target agent.
128 /*!
129 * Note that this reference should be used with care.
130 * In complex scenarios an agent can be deregistered and
131 * this reference can point to free memory or reallocated
132 * for another agent memory.
133 *
134 * Before access to that reference it is necessary to check
135 * m_target_id field.
136 */
137 const agent_t & m_target_agent;
138 //! The unique ID or target's direct mbox.
139 /*!
140 * ID of mbox is a unique value. ID is not reused even if
141 * the agent is destroyed and its memory is reallocated for
142 * another agent.
143 */
144 const mbox_id_t m_target_id;
145 //! Type of message/signal to activate the trigger.
146 const std::type_index m_msg_type;
147 //! ID of source mbox of message/signal to activate the trigger.
148 const mbox_id_t m_src_mbox_id;
149
150 //! Optional function for completion of the trigger.
151 /*!
152 * If m_completion is empty then there is no need to
153 * separate completion action for the trigger and trigger
154 * become completed just after activation.
155 */
156 completion_function_t m_completion;
157
158 // Note. These are required by Clang.
159 trigger_t( const trigger_t & ) = delete;
160 trigger_t( trigger_t && ) = delete;
161
162 public :
163 //! Initializing constructor.
164 trigger_t(
165 incident_status_t incident_status,
166 const agent_t & target,
167 std::type_index msg_type,
168 mbox_id_t src_mbox_id );
169 ~trigger_t();
170
171 //! Get the reference of the target agent.
172 /*!
173 * \attention
174 * This method should be used with care because if target agent
175 * is deregistered then a dangling reference will be returned.
176 */
177 [[nodiscard]]
178 const agent_t &
179 target_agent() const noexcept;
180
181 //! Setter for completion function.
182 void
183 set_completion( completion_function_t fn );
184
185 //! Check for activation of the trigger.
186 /*!
187 * \retval true Trigger is activated.
188 * \retval false Trigger is not activated and information about
189 * the event can be used for checking for other triggers.
190 */
191 [[nodiscard]]
192 bool
193 check(
194 //! What happened with message/signal?
195 const incident_status_t incident_status,
196 //! Context for that event.
197 const incident_info_t & info ) const noexcept;
198
199 //! Does this trigger require separate completion action?
200 [[nodiscard]]
201 bool
202 requires_completion() const noexcept;
203
204 //! Do completion of a trigger.
205 void
206 complete(
207 const trigger_completion_context_t & context ) noexcept;
208 };
209
210 /*!
211 * \brief An alias for unique_ptr of trigger.
212 *
213 * \since
214 * v.5.5.24
215 */
216 using trigger_unique_ptr_t = std::unique_ptr< trigger_t >;
217
218 /*!
219 * \brief An alias for type of tigger's container.
220 *
221 * \since
222 * v.5.5.24
223 */
224 using trigger_container_t = std::vector< trigger_unique_ptr_t >;
225
226 /*!
227 * \brief A special data class with partial info for a new trigger.
228 *
229 * This data class contains a type of message/signal and optional
230 * mbox_id for source mbox. If mbox_id is not specified then the direct
231 * mbox of a target agent will be used as source mbox.
232 *
233 * \since
234 * v.5.5.24
235 */
236 template< incident_status_t Status >
237 struct trigger_source_t final
238 {
239 std::type_index m_msg_type;
240 optional<mbox_id_t> m_src_mbox_id;
241
trigger_source_tso_5::experimental::testing::v1::details::trigger_source_t242 trigger_source_t(
243 std::type_index msg_type,
244 mbox_id_t src_mbox_id)
245 : m_msg_type( std::move(msg_type) )
246 , m_src_mbox_id( src_mbox_id )
247 {}
248
trigger_source_tso_5::experimental::testing::v1::details::trigger_source_t249 trigger_source_t(
250 std::type_index msg_type )
251 : m_msg_type( std::move(msg_type) )
252 {}
253 };
254
255 /*!
256 * \brief A special data object for case of store-state-name completion action.
257 *
258 * \since
259 * v.5.5.24
260 */
261 struct store_agent_state_name_t
262 {
263 //! Name of tag for store-state-name action.
264 std::string m_tag;
265 };
266
267 /*!
268 * \brief An interface of step's constraints.
269 *
270 * \since
271 * v.5.5.24
272 */
273 class constraint_t
274 {
275 public :
276 constraint_t() = default;
277 virtual ~constraint_t() noexcept = default;
278
279 constraint_t( const constraint_t & ) = delete;
280 constraint_t & operator=( const constraint_t & ) = delete;
281
282 constraint_t( constraint_t && ) = delete;
283 constraint_t & operator=( constraint_t && ) = delete;
284
285 //! Hook for step preactivation.
286 /*!
287 * This hook will be called when step is preactivated.
288 * Constraint object can do some initial actions here. For
289 * example the current timestamp can be store. Or some resources
290 * can be acquired.
291 */
292 virtual void
293 start() noexcept = 0;
294
295 //! Hook for step completion.
296 /*!
297 * This hook will be called when step is completed.
298 * Constraint object can do some cleanup actions here. For example
299 * resources acquired in start() method can be released.
300 */
301 virtual void
302 finish() noexcept = 0;
303
304 //! Check for fulfillment of constraint.
305 /*!
306 * \retval true If constraint fulfilled.
307 * \retval false If constraint is not fulfilled and an incident
308 * should be ignored.
309 */
310 [[nodiscard]]
311 virtual bool
312 check(
313 //! What happened with message/signal?
314 const incident_status_t incident_status,
315 //! Context for that event.
316 const incident_info_t & info ) const noexcept = 0;
317 };
318
319 /*!
320 * \brief An alias for unique_ptr of constraint.
321 *
322 * \since
323 * v.5.5.24
324 */
325 using constraint_unique_ptr_t = std::unique_ptr< constraint_t >;
326
327 /*!
328 * \brief An alias for container of constraints.
329 *
330 * \since
331 * v.5.5.24
332 */
333 using constraint_container_t = std::vector< constraint_unique_ptr_t >;
334
335 /*!
336 * \brief Implementation of 'not_before' constraint.
337 *
338 * \since
339 * v.5.5.24
340 */
341 class not_before_constraint_t final
342 : public constraint_t
343 {
344 //! Value to be used.
345 const std::chrono::steady_clock::duration m_pause;
346
347 //! Time point of step preactivation.
348 /*!
349 * Receives value only in start() method.
350 */
351 std::chrono::steady_clock::time_point m_started_at;
352
353 public :
not_before_constraint_t(std::chrono::steady_clock::duration pause)354 not_before_constraint_t(
355 std::chrono::steady_clock::duration pause )
356 : m_pause( pause )
357 {}
358
359 void
start()360 start() noexcept override
361 {
362 m_started_at = std::chrono::steady_clock::now();
363 }
364
365 void
finish()366 finish() noexcept override { /* nothing to do */ }
367
368 [[nodiscard]]
369 bool
check(const incident_status_t,const incident_info_t &) const370 check(
371 const incident_status_t /*incident_status*/,
372 const incident_info_t & /*info*/ ) const noexcept override
373 {
374 return m_started_at + m_pause <=
375 std::chrono::steady_clock::now();
376 }
377 };
378
379 /*!
380 * \brief Implementation of 'not_after' constraint.
381 *
382 * \since
383 * v.5.5.24
384 */
385 class not_after_constraint_t final
386 : public constraint_t
387 {
388 //! Value to be used.
389 const std::chrono::steady_clock::duration m_pause;
390
391 //! Time point of step preactivation.
392 /*!
393 * Receives value only in start() method.
394 */
395 std::chrono::steady_clock::time_point m_started_at;
396
397 public :
not_after_constraint_t(std::chrono::steady_clock::duration pause)398 not_after_constraint_t(
399 std::chrono::steady_clock::duration pause )
400 : m_pause( pause )
401 {}
402
403 void
start()404 start() noexcept override
405 {
406 m_started_at = std::chrono::steady_clock::now();
407 }
408
409 void
finish()410 finish() noexcept override { /* nothing to do */ }
411
412 [[nodiscard]]
413 bool
check(const incident_status_t,const incident_info_t &) const414 check(
415 const incident_status_t /*incident_status*/,
416 const incident_info_t & /*info*/ ) const noexcept override
417 {
418 return m_started_at + m_pause >
419 std::chrono::steady_clock::now();
420 }
421 };
422
423 /*!
424 * \brief An alias for type of step's preactivation action.
425 *
426 * \since
427 * v.5.5.24
428 */
429 using preactivate_action_t = std::function< void() /*noexcept*/ >;
430
431 /*!
432 * \brief An interface of testing scenario step.
433 *
434 * This interface is described in a public header file just for
435 * definition of step_definition_proxy_t class. But abstract_scenario_step_t
436 * is an internal and implementation-specific type, please don't use
437 * it in end-user code.
438 *
439 * \since
440 * v.5.5.24
441 */
442 class SO_5_TYPE abstract_scenario_step_t
443 {
444 public :
445 //! Status of step.
446 enum class status_t
447 {
448 //! Step is not preactivated yet.
449 //! Step can be changed while it in this state.
450 passive,
451 //! Step is preactivated.
452 //! It means that this step is the current step in testing
453 //! scenario now. But not all required triggers are activated
454 //! yet.
455 preactivated,
456 //! Step is activated.
457 //! It means that all required triggers are activated, but
458 //! some triggers wait for completion actions.
459 active,
460 //! Step is completed.
461 //! It means that all required triggers were activated and
462 //! all of them were completed.
463 completed
464 };
465
466 /*!
467 * \brief Type of token returned from pre-handler-hook.
468 *
469 * This token can be in valid or invalid states. If is is in
470 * valid state then it should be passed as is to
471 * abstract_scenario_step_t::post_handler_hook() method.
472 *
473 * \since
474 * v.5.5.24
475 */
476 class token_t final
477 {
478 //! Activated trigger.
479 /*!
480 * Can be null. It means that there was no activated triggers
481 * at pre_handler_hook() method. And token is in invalid state.
482 *
483 * This pointer can also be null if trigger was activated but
484 * wasn't require completion. It means that trigger switched
485 * to completed state and there is no need in separate
486 * completion action.
487 *
488 * If this pointer is not null then post_handler_hook()
489 * should be called for scenario step.
490 */
491 trigger_t * m_trigger;
492
493 public:
token_t()494 token_t() noexcept
495 : m_trigger( nullptr )
496 {}
token_t(trigger_t * trigger)497 token_t( trigger_t * trigger ) noexcept
498 : m_trigger( trigger )
499 {}
500
501 [[nodiscard]]
502 bool
valid() const503 valid() const noexcept { return m_trigger != nullptr; }
504
505 //! Get a reference to activated trigger.
506 /*!
507 * This method should be called only if token is in valid
508 * state.
509 */
510 [[nodiscard]]
511 trigger_t &
trigger() const512 trigger() const noexcept { return *m_trigger; }
513 };
514
515 // Note. These are required by Clang compiler.
516 abstract_scenario_step_t() = default;
517 abstract_scenario_step_t( const abstract_scenario_step_t & ) = delete;
518 abstract_scenario_step_t & operator=( const abstract_scenario_step_t & ) = delete;
519 abstract_scenario_step_t( abstract_scenario_step_t && ) = delete;
520 abstract_scenario_step_t & operator=( abstract_scenario_step_t && ) = delete;
521
522 virtual ~abstract_scenario_step_t() = default;
523
524 //! Get the name of the step.
525 [[nodiscard]]
526 virtual const std::string &
527 name() const noexcept = 0;
528
529 //! Perform preactivation of the step.
530 /*!
531 * Preactivation means that the step becomes the current step
532 * of the scenario and all events will go to
533 * pre_handler_hook(), post_handler_hook() and
534 * no_handler_hook() of that step.
535 *
536 * This method should call all preactivation actions passed to
537 * the step via add_peactivate_action() method.
538 */
539 virtual void
540 preactivate() noexcept = 0;
541
542 //! Hook that should be called before invocation of event-handler.
543 /*!
544 * This hook should be called before invocation of any event-handler
545 * for ordinary message, service request or enveloped message.
546 *
547 * The step should update its status inside that method.
548 *
549 * If a valid token is returned then this token should be passed
550 * to subsequent call to post_handler_hook() method.
551 */
552 [[nodiscard]]
553 virtual token_t
554 pre_handler_hook(
555 const incident_info_t & info ) noexcept = 0;
556
557 //! Hook that should be called just after completion of event-handler.
558 /*!
559 * If previous call to pre_handler_hook() returned a valid
560 * token object then this hook should be called just after
561 * completion of event-handler.
562 */
563 virtual void
564 post_handler_hook(
565 const scenario_in_progress_accessor_t & scenario_accessor,
566 token_t token ) noexcept = 0;
567
568 //! Hook that should be called if there is no event-handler for
569 //! a message or service request.
570 /*!
571 * The step should update its status inside that method.
572 */
573 virtual void
574 no_handler_hook(
575 const incident_info_t & info ) noexcept = 0;
576
577 //! Get the current status of the step.
578 [[nodiscard]]
579 virtual status_t
580 status() const noexcept = 0;
581
582 //! Add another preactivation action.
583 /*!
584 * Can be called several times. New action will be added to the end
585 * of list of preactivation actions.
586 */
587 virtual void
588 add_preactivate_action(
589 preactivate_action_t action ) = 0;
590
591 //! Setup triggers for the step.
592 /*!
593 * It is assumed that this method will be called only once per step.
594 * If it is called several times the new information will replace
595 * the old one.
596 *
597 * \note
598 * Value of \a triggers_to_activate has sence for case of
599 * step_definition_proxy_t::when_any() method: there can be
600 * several triggers in \a triggers container, but only one of
601 * them has to be triggered to activate the step.
602 */
603 virtual void
604 setup_triggers(
605 trigger_container_t triggers,
606 std::size_t triggers_to_activate ) noexcept = 0;
607
608 //! Setup constraints for the step.
609 /*!
610 * It is assumed that this method will be called only once per
611 * step. If it is called several times the new information will
612 * replace the old one.
613 */
614 virtual void
615 setup_constraints(
616 constraint_container_t constraints ) noexcept = 0;
617 };
618
619 /*!
620 * \brief An alias for unique_ptr of scenario-step.
621 *
622 * \since
623 * v.5.5.24
624 */
625 using step_unique_ptr_t = std::unique_ptr< abstract_scenario_step_t >;
626
627 /*!
628 * \brief A helper class for holding unique_ptr to a trigger while
629 * trigger is being configured.
630 *
631 * \note
632 * This class is Movable, but not Copyable.
633 *
634 * \since
635 * v.5.5.24
636 */
637 template< incident_status_t Status >
638 class trigger_holder_t final
639 {
640 trigger_unique_ptr_t m_trigger;
641
642 public :
trigger_holder_t(trigger_unique_ptr_t trigger)643 trigger_holder_t( trigger_unique_ptr_t trigger ) noexcept
644 : m_trigger( std::move(trigger) )
645 {}
646
647 trigger_holder_t( trigger_holder_t && ) noexcept = default;
648 trigger_holder_t & operator=( trigger_holder_t && ) noexcept = default;
649
650 trigger_holder_t( const trigger_holder_t & ) = delete;
651 trigger_holder_t & operator=( const trigger_holder_t & ) = delete;
652
653 //! Get the trigger object from the holder.
654 /*!
655 * \note
656 * Holder becomes empty and should not be used anymore
657 * (except for assigning a new value);
658 */
659 trigger_unique_ptr_t
giveout_trigger()660 giveout_trigger() noexcept
661 {
662 return std::move(m_trigger);
663 }
664 };
665
666 } /* namespace details */
667
668 /*!
669 * \brief A special object that should be used for definition of a step
670 * of a testing scenario.
671 *
672 * Usage example:
673 * \code
674 * using namespace so_5::experimental::testing;
675 * testing_env_t env;
676 *
677 * so_5::agent_t * test_agent = ...;
678 * so_5::agent_t * another_agent = ...;
679 *
680 * auto scenario = env.scenario();
681 *
682 * // Create a simple step. It triggers when agent receives a message
683 * // from its direct mbox.
684 * scenario.define_step("one").when(*test_agent & reacts_to<some_message>());
685 *
686 * // Create a step with constraint: a message should be received only
687 * // after a 15ms from preactivation of this step.
688 * scenario.define_step("two")
689 * .constraints(not_before(std::chrono::milliseconds(15)))
690 * .when(*test_agent & reacts_to<another_message>(some_mbox));
691 *
692 * // Create a step with initial actions: several messages will be sent
693 * // during preactivation of this step.
694 * // There are also two constraints: a time range for receiving expected
695 * // message.
696 * scenario.define_step("three")
697 * .impact<first_message>(first_target)
698 * .impact<second_message>(second_target, arg1, arg2, arg3)
699 * .constraints(
700 * not_before(std::chrono::milliseconds(15)),
701 * not_after(std::chrono::seconds(2)))
702 * .when(*test_agent & reacts_to<expected_message>(some_mbox));
703 *
704 * // Create a step that trigges when both agents receive messages.
705 * scenario.define_step("four")
706 * .when_all(
707 * *test_agent & reacts_to<one_message>(some_mbox),
708 * *another_agent & reacts_to<different_message>(another_mbox));
709 *
710 * // Create a step that triggers when one of agents receive a message.
711 * scenario.define_step("five")
712 * .when_any(
713 * *test_agent & reacts_to<one_message>(some_mbox),
714 * *another_agent & reacts_to<different_message>(another_mbox));
715 * \endcode
716 *
717 * \note
718 * The object of this type can be stored to define a step in several
719 * footsteps:
720 * \code
721 * auto step = env.scenario().define_step("my_step");
722 * if(some_condition)
723 * step.constraints(...);
724 * if(another_condition)
725 * step.impact(...);
726 * if(third_condition)
727 * step.when(...);
728 * else
729 * step.when_all(...);
730 * \endcode
731 * But all definitions should be done before calling of
732 * scenario_proxy_t::run_for().
733 *
734 * \attention
735 * This class is not thread-safe. Because of that it should be used on
736 * a context of a signle thread.
737 *
738 * \since
739 * v.5.5.24
740 */
741 class step_definition_proxy_t
742 {
743 details::abstract_scenario_step_t * m_step;
744
745 void
append_trigger_to(details::trigger_container_t &)746 append_trigger_to( details::trigger_container_t & /*to*/ ) {}
747
748 template<
749 details::incident_status_t Status,
750 typename... Args >
751 void
append_trigger_to(details::trigger_container_t & to,details::trigger_holder_t<Status> event,Args &&...args)752 append_trigger_to(
753 details::trigger_container_t & to,
754 details::trigger_holder_t<Status> event,
755 Args && ...args )
756 {
757 to.emplace_back( event.giveout_trigger() );
758 append_trigger_to( to, std::forward<Args>(args)... );
759 }
760
761 void
append_constraint_to(details::constraint_container_t &)762 append_constraint_to( details::constraint_container_t & /*to*/ ) {}
763
764 template< typename... Args >
765 void
append_constraint_to(details::constraint_container_t & to,details::constraint_unique_ptr_t head,Args &&...tail)766 append_constraint_to(
767 details::constraint_container_t & to,
768 details::constraint_unique_ptr_t head,
769 Args && ...tail )
770 {
771 to.emplace_back( std::move(head) );
772 append_constraint_to( to, std::forward<Args>(tail)... );
773 }
774
775 public :
776 //! Initializing constructor.
777 /*!
778 * Despite the fact that this is a public constructor
779 * it is a part of SObjectizer implementation and is subject
780 * of change without a notice. Don't use it in end-user code.
781 */
step_definition_proxy_t(details::abstract_scenario_step_t * step)782 step_definition_proxy_t(
783 details::abstract_scenario_step_t * step )
784 : m_step( step )
785 {}
786
787 /*!
788 * \brief Define a preactivation action in form of
789 * sending a message/signal to the specified target.
790 *
791 * This method creates and stores an instance of a message/signal
792 * and then sends this instance when step is preactivated.
793 *
794 * Usage example:
795 * \code
796 * env.scenario().define_step("my_step")
797 * .impact<my_message>(*test_agent, arg1, arg2, arg3)
798 * .impact<my_signal>(some_mbox)
799 * .impact<another_message>(mchain)
800 * ...;
801 * \endcode
802 *
803 * Please note that this method can be called several times.
804 *
805 * \tparam Msg_Type type of a message/signal to send.
806 * \tparam Target type of a target for message/signal.
807 * It can be a reference to mbox, a reference to agent
808 * (agent's direct mbox will be used in that case), or
809 * a reference to mchain.
810 * \tparam Args types of arguments for Msg_Type's constructor.
811 */
812 template<
813 typename Msg_Type,
814 typename Target,
815 typename... Args >
816 step_definition_proxy_t &
impact(Target && target,Args &&...args)817 impact( Target && target, Args && ...args )
818 {
819 // Deduce actual mbox of the recevier.
820 // This mbox will be captured by lambda function.
821 auto to = so_5::send_functions_details::arg_to_mbox(
822 std::forward<Target>(target) );
823
824 // Make an instance of a message.
825 // This instance will be captured by lambda function.
826 message_ref_t msg{
827 so_5::details::make_message_instance<Msg_Type>(
828 std::forward<Args>(args)... )
829 };
830 // Mutability of a message should be changed appropriately.
831 so_5::details::mark_as_mutable_if_necessary<Msg_Type>( msg );
832
833 // Now we can create a lambda-function that will send
834 // the message instance at the appropriate time.
835 m_step->add_preactivate_action(
836 [to = std::move(to), msg = std::move(msg)]() noexcept {
837 using namespace so_5::low_level_api;
838 deliver_message(
839 *to,
840 message_payload_type< Msg_Type >
841 ::subscription_type_index(),
842 std::move(msg) );
843 } );
844
845 return *this;
846 }
847
848 /*!
849 * \brief Add preactivation action in form of lambda-object.
850 *
851 * This method can be used for non-trivial actions on step
852 * preactivation, like sending enveloped messages.
853 *
854 * Usage example:
855 * \code
856 * env.scenario().define_step("my_step")
857 * .impact([some_mbox, some_data] {
858 * some_mbox->do_deliver_enveloped_msg(
859 * so_5::message_payload_type<my_message>::subscription_type_index(),
860 * std::make_unique<my_envelope<my_message>>(some_data),
861 * 1u);
862 * });
863 * \endcode
864 *
865 * Please note that this method can be called several times.
866 *
867 * \note
868 * Preactivation of scenario step is performed when scenario object
869 * is locked. Becase of that actions inside \a lambda must be performed
870 * with care to avoid deadlocks or blocking of scenario for too long.
871 *
872 * \attention
873 * \a lambda should be noexcept-function.
874 */
875 template< typename Lambda >
876 step_definition_proxy_t &
impact(Lambda && lambda)877 impact( Lambda && lambda )
878 {
879 m_step->add_preactivate_action(
880 [lambda]() noexcept { lambda(); } );
881
882 return *this;
883 }
884
885 /*!
886 * \brief Add a tigger for activation of that step
887 *
888 * Step is activated when this trigger is activated.
889 *
890 * Usage example:
891 * \code
892 * env.scenario().define_step("my_step")
893 * .when(some_agent & reacts_to<my_message>());
894 * \endcode
895 *
896 * \note
897 * This method is indended to be called only once.
898 * All subsequent calls to that method will replace triggers
899 * those where set by previous calls to when(), when_all() and
900 * when_any() methods.
901 */
902 template< details::incident_status_t Status >
903 step_definition_proxy_t &
when(details::trigger_holder_t<Status> event)904 when(
905 details::trigger_holder_t<Status> event )
906 {
907 details::trigger_container_t cnt;
908 cnt.emplace_back( event.giveout_trigger() );
909
910 m_step->setup_triggers( std::move(cnt), 1u );
911
912 return *this;
913 }
914
915 /*!
916 * \brief Add a list of tiggers for activation of that step
917 *
918 * Step is activated when \b any of those triggers is activated.
919 *
920 * Usage example:
921 * \code
922 * env.scenario().define_step("my_step")
923 * .when_any(
924 * some_agent & reacts_to<my_message>(),
925 * another_agent & reacts_to<another_message>(),
926 * yet_another_agent & reacts_to<yet_another_message>());
927 * \endcode
928 *
929 * \note
930 * This method is indended to be called only once.
931 * All subsequent calls to that method will replace triggers
932 * those where set by previous calls to when(), when_all() and
933 * when_any() methods.
934 */
935 template<
936 details::incident_status_t Status,
937 typename... Args >
938 step_definition_proxy_t &
when_any(details::trigger_holder_t<Status> event,Args &&...args)939 when_any(
940 details::trigger_holder_t<Status> event,
941 Args && ...args )
942 {
943 details::trigger_container_t cnt;
944 cnt.reserve( 1u + sizeof...(args) );
945
946 append_trigger_to(
947 cnt,
948 std::move(event),
949 std::forward<Args>(args)... );
950
951 m_step->setup_triggers( std::move(cnt), 1u );
952
953 return *this;
954 }
955
956 /*!
957 * \brief Add a list of tiggers for activation of that step
958 *
959 * Step is activated when \a all of those triggers is activated.
960 *
961 * Usage example:
962 * \code
963 * env.scenario().define_step("my_step")
964 * .when_all(
965 * some_agent & reacts_to<my_message>(),
966 * another_agent & reacts_to<another_message>(),
967 * yet_another_agent & reacts_to<yet_another_message>());
968 * \endcode
969 *
970 * \note
971 * This method is indended to be called only once.
972 * All subsequent calls to that method will replace triggers
973 * those where set by previous calls to when(), when_all() and
974 * when_any() methods.
975 */
976 template<
977 details::incident_status_t Status,
978 typename... Args >
979 step_definition_proxy_t &
when_all(details::trigger_holder_t<Status> event,Args &&...args)980 when_all(
981 details::trigger_holder_t<Status> event,
982 Args && ...args )
983 {
984 const auto total_triggers = 1u + sizeof...(args);
985
986 details::trigger_container_t cnt;
987 cnt.reserve( total_triggers );
988
989 append_trigger_to(
990 cnt,
991 std::move(event),
992 std::forward<Args>(args)... );
993
994 m_step->setup_triggers( std::move(cnt), total_triggers );
995
996 return *this;
997 }
998
999 /*!
1000 * \brief Add a list of constraints for that step
1001 *
1002 * All specified constraints should be fulfilled to enable
1003 * activation of that step.
1004 *
1005 * Usage example:
1006 * \code
1007 * env.scenario().define_step("my_step")
1008 * .constraints(
1009 * not_before(std::chrono::milliseconds(10)))
1010 * ...;
1011 * env.scenario().define_step("another_step")
1012 * .constraints(
1013 * not_after(std::chrono::milliseconds(500),
1014 * not_before(std::chrono::milliseconds(10)))
1015 * ...;
1016 * \endcode
1017 *
1018 * \note
1019 * This method is indended to be called only once.
1020 * All subsequent calls to that method will replace constraints
1021 * those where set by previous calls to constraints().
1022 */
1023 template< typename... Args >
1024 step_definition_proxy_t &
constraints(details::constraint_unique_ptr_t head,Args &&...tail)1025 constraints(
1026 details::constraint_unique_ptr_t head,
1027 Args && ...tail )
1028 {
1029 details::constraint_container_t cnt;
1030 cnt.reserve( 1u + sizeof...(tail) );
1031
1032 append_constraint_to(
1033 cnt,
1034 std::move(head),
1035 std::forward<Args>(tail)... );
1036
1037 m_step->setup_constraints( std::move(cnt) );
1038
1039 return *this;
1040 }
1041 };
1042
1043 /*!
1044 * \brief Status of testing scenario.
1045 *
1046 * This enumeration is used by testing scenario itself and by
1047 * scenario_result_t type.
1048 *
1049 * \since
1050 * v.5.5.24
1051 */
1052 enum class scenario_status_t
1053 {
1054 //! Testing scenario is not started yet.
1055 //! New step can be added when scenario is in state.
1056 not_started,
1057 //! Testing scenario is started but not finished yet.
1058 //! New steps can't be added.
1059 in_progress,
1060 //! Testing scenario is successfuly completed.
1061 completed,
1062 //! Testing scenario is not working any more, but it is not
1063 //! completed becase there is no more time to run the scenario.
1064 timed_out
1065 };
1066
1067 /*!
1068 * \brief The result of run of testing scenario.
1069 *
1070 * The result contains the status of the scenario
1071 * (in form of scenario_status_t enumeration) and optional textual
1072 * description of the result.
1073 *
1074 * Note that description is missing if testing scenario completed
1075 * successfuly (it means that scenario has scenario_status_t::completed
1076 * state after completion of scenario_proxy_t::run_for() method).
1077 *
1078 * The content and format of textual description is not specified and
1079 * can be changed in the future versions of SObjectizer.
1080 *
1081 * Usage example:
1082 * \code
1083 * TEST_CASE("some_case") {
1084 * using namespace so_5::experimental::testing;
1085 *
1086 * testing_env_t env;
1087 * ...
1088 * env.scenario().run_for(std::chrono::milliseconds(500));
1089 *
1090 * REQUIRE(completed() == env.scenario().result());
1091 * }
1092 * \endcode
1093 *
1094 * \note
1095 * Object of scenario_result_t type can be dumped to std::ostream.
1096 * For example:
1097 * \code
1098 * auto result = env.scenario().result();
1099 * if(completed() != result)
1100 * std::cout << "The result is: " << result << std::endl;
1101 * \endcode
1102 *
1103 * \since
1104 * v.5.5.24
1105 */
1106 class scenario_result_t
1107 {
1108 scenario_status_t m_status;
1109 optional< std::string > m_description;
1110
1111 public :
1112 //! The constructor for a case when there is only status of scenario.
scenario_result_t(scenario_status_t status)1113 scenario_result_t(
1114 scenario_status_t status )
1115 : m_status( status )
1116 {}
1117 //! The constructor for a case when there are status and description
1118 //! of scenario.
scenario_result_t(scenario_status_t status,std::string description)1119 scenario_result_t(
1120 scenario_status_t status,
1121 std::string description )
1122 : m_status( status )
1123 , m_description( std::move(description) )
1124 {}
1125
1126 //! Check for equality.
1127 /*!
1128 * \note
1129 * Only status is compared.
1130 */
1131 [[nodiscard]]
1132 bool
operator ==(const scenario_result_t & o) const1133 operator==( const scenario_result_t & o ) const noexcept
1134 {
1135 return m_status == o.m_status;
1136 }
1137
1138 //! Check for inequality.
1139 /*!
1140 * \note
1141 * Only status is compared.
1142 */
1143 [[nodiscard]]
1144 bool
operator !=(const scenario_result_t & o) const1145 operator!=( const scenario_result_t & o ) const noexcept
1146 {
1147 return m_status != o.m_status;
1148 }
1149
1150 //! Dump of object's content to ostream.
1151 friend std::ostream &
operator <<(std::ostream & to,const scenario_result_t & v)1152 operator<<( std::ostream & to, const scenario_result_t & v )
1153 {
1154 const auto status_name =
1155 [](scenario_status_t st) -> const char * {
1156 const char * result{};
1157 switch( st )
1158 {
1159 case scenario_status_t::not_started :
1160 result = "not_started";
1161 break;
1162 case scenario_status_t::in_progress :
1163 result = "in_progress";
1164 break;
1165 case scenario_status_t::completed :
1166 result = "completed";
1167 break;
1168 case scenario_status_t::timed_out :
1169 result = "timed_out";
1170 break;
1171 }
1172 return result;
1173 };
1174
1175 to << "[" << status_name( v.m_status );
1176 if( v.m_description )
1177 to << ",{" << *v.m_description << "}";
1178 to << "]";
1179
1180 return to;
1181 }
1182 };
1183
1184 /*!
1185 * \brief Create a value that means that scenario completed successfuly.
1186 *
1187 * Usage example:
1188 * \code
1189 * TEST_CASE("some_case") {
1190 * using namespace so_5::experimental::testing;
1191 *
1192 * testing_env_t env;
1193 * ...
1194 * env.scenario().run_for(std::chrono::milliseconds(500));
1195 *
1196 * REQUIRE(completed() == env.scenario().result());
1197 * }
1198 * \endcode
1199 *
1200 * \since
1201 * v.5.5.24
1202 */
1203 [[nodiscard]]
1204 inline scenario_result_t
completed()1205 completed() { return { scenario_status_t::completed }; }
1206
1207 /*!
1208 * \brief Define a trigger that activates when an agent receives and
1209 * handles a message from the direct mbox.
1210 *
1211 * Usage example:
1212 * \code
1213 * using namespace so_5::experimental::testing;
1214 * ...
1215 * env.scenario().define_step("my_step")
1216 * .when(some_agent & reacts_to<some_message>());
1217 * \endcode
1218 *
1219 * \since
1220 * v.5.5.24
1221 */
1222 template<typename Msg_Type>
1223 details::trigger_source_t< details::incident_status_t::handled >
reacts_to()1224 reacts_to()
1225 {
1226 return { message_payload_type<Msg_Type>::subscription_type_index() };
1227 }
1228
1229 /*!
1230 * \brief Define a trigger that activates when an agent receives and
1231 * handles a message from the specific mbox.
1232 *
1233 * Usage example:
1234 * \code
1235 * using namespace so_5::experimental::testing;
1236 * ...
1237 * env.scenario().define_step("my_step")
1238 * .when(some_agent & reacts_to<some_message>(some_mbox));
1239 * \endcode
1240 *
1241 * \since
1242 * v.5.5.24
1243 */
1244 template<typename Msg_Type>
1245 details::trigger_source_t< details::incident_status_t::handled >
reacts_to(const so_5::mbox_t & mbox)1246 reacts_to( const so_5::mbox_t & mbox )
1247 {
1248 return {
1249 message_payload_type<Msg_Type>::subscription_type_index(),
1250 mbox->id()
1251 };
1252 }
1253
1254 /*!
1255 * \brief Create a special marker for a trigger for storing
1256 * agent's state name inside scenario.
1257 *
1258 * Usage example:
1259 * \code
1260 * using namespace so_5::experimental::testing;
1261 * ...
1262 * env.scenario().define_step("my_step")
1263 * .when(some_agent & reacts_to<some_message>() & store_agent_name("my_agent"));
1264 * ...
1265 * env.scenario().run_for(std::chrono::seconds(1));
1266 *
1267 * REQUIRE(completed() == env.scenario().result());
1268 * REQUIRE("some_state" == env.scenario().stored_state_name("my_step", "my_agent"));
1269 * \endcode
1270 *
1271 * \since
1272 * v.5.5.24
1273 */
1274 inline details::store_agent_state_name_t
store_state_name(std::string tag)1275 store_state_name( std::string tag )
1276 {
1277 return { std::move(tag) };
1278 }
1279
1280 /*!
1281 * \brief Define a trigger that activates when an agent rejects a message from
1282 * the direct mbox.
1283 *
1284 * Usage example:
1285 * \code
1286 * using namespace so_5::experimental::testing;
1287 * ...
1288 * env.scenario().define_step("my_step")
1289 * .when(some_agent & ignores<some_message>());
1290 * \endcode
1291 *
1292 * \attention
1293 * It is necessary that agent should be subscribed to that message but
1294 * ignores it in the current state.
1295 * If an agent is not subscribed to a message then the message can be
1296 * simply skipped inside a call to send() function. It means that there
1297 * won't be delivery of message at all.
1298 *
1299 * \since
1300 * v.5.5.24
1301 */
1302 template<typename Msg_Type>
1303 details::trigger_source_t< details::incident_status_t::ignored >
ignores()1304 ignores()
1305 {
1306 return { message_payload_type<Msg_Type>::subscription_type_index() };
1307 }
1308
1309 /*!
1310 * \brief Define a trigger that activates when an agent rejects a message from
1311 * the direct mbox.
1312 *
1313 * Usage example:
1314 * \code
1315 * using namespace so_5::experimental::testing;
1316 * ...
1317 * env.scenario().define_step("my_step")
1318 * .when(some_agent & ignores<some_message>(some_mbox));
1319 * \endcode
1320 *
1321 * \attention
1322 * It is necessary that agent should be subscribed to that message but
1323 * ignores it in the current state.
1324 * If an agent is not subscribed to a message then the message can be
1325 * simply skipped inside a call to send() function. It means that there
1326 * won't be delivery of message at all.
1327 *
1328 * \since
1329 * v.5.5.24
1330 */
1331 template<typename Msg_Type>
1332 details::trigger_source_t< details::incident_status_t::ignored >
ignores(const so_5::mbox_t & mbox)1333 ignores( const so_5::mbox_t & mbox )
1334 {
1335 return {
1336 message_payload_type<Msg_Type>::subscription_type_index(),
1337 mbox->id()
1338 };
1339 }
1340
1341 /*!
1342 * \brief Create a constraint not-before.
1343 *
1344 * That constraint is fulfilled if an event is happened after
1345 * a specified \a pause. Time is calculated from moment of preactivation
1346 * of a scenario's step.
1347 *
1348 * Usage example:
1349 * \code
1350 * using namespace so_5::experimental::testing;
1351 * env.scenario().define_step("my_step")
1352 * .constraints(not_before(std::chrono::milliseconds(50)))
1353 * .when(some_agent & reacts_to<some_message>());
1354 * \endcode
1355 * In that case step won't be activated if agent receives a message
1356 * after, for example, 15ms.
1357 *
1358 * \note
1359 * If not_before() is used with not_after() then the correctness of those
1360 * constraints is not checked.
1361 *
1362 * \since
1363 * v.5.5.24
1364 */
1365 inline details::constraint_unique_ptr_t
not_before(std::chrono::steady_clock::duration pause)1366 not_before(
1367 std::chrono::steady_clock::duration pause )
1368 {
1369 return std::make_unique< details::not_before_constraint_t >(pause);
1370 }
1371
1372 /*!
1373 * \brief Create a constraint not-after.
1374 *
1375 * That constraint is fulfilled if an event is happened before
1376 * a specified \a pause. Time is calculated from moment of preactivation
1377 * of a scenario's step.
1378 *
1379 * Usage example:
1380 * \code
1381 * using namespace so_5::experimental::testing;
1382 * env.scenario().define_step("my_step")
1383 * .constraints(not_after(std::chrono::milliseconds(50)))
1384 * .when(some_agent & reacts_to<some_message>());
1385 * \endcode
1386 * In that case step won't be activated if agent receives a message
1387 * after, for example, 55ms.
1388 *
1389 * \note
1390 * If not_after() is used with not_before() then the correctness of those
1391 * constraints is not checked.
1392 *
1393 * \since
1394 * v.5.5.24
1395 */
1396 inline details::constraint_unique_ptr_t
not_after(std::chrono::steady_clock::duration pause)1397 not_after(
1398 std::chrono::steady_clock::duration pause )
1399 {
1400 return std::make_unique< details::not_after_constraint_t >(pause);
1401 }
1402
1403 namespace details {
1404
1405 /*!
1406 * \brief A special objects that allows to call some specific methods
1407 * of a testing scenario.
1408 *
1409 * In the current version abstract_scenario_t has at least one
1410 * method that should be called only when testing scenario is in
1411 * progess. This method requires an instance of that class as a special
1412 * token.
1413 *
1414 * \since
1415 * v.5.5.24
1416 */
1417 class scenario_in_progress_accessor_t final
1418 {
1419 friend class abstract_scenario_t;
1420
1421 private :
1422 outliving_reference_t<abstract_scenario_t> m_scenario;
1423
scenario_in_progress_accessor_t(outliving_reference_t<abstract_scenario_t> scenario)1424 scenario_in_progress_accessor_t(
1425 outliving_reference_t<abstract_scenario_t> scenario )
1426 : m_scenario( scenario )
1427 {}
1428
1429 scenario_in_progress_accessor_t(
1430 const scenario_in_progress_accessor_t & ) = delete;
1431 scenario_in_progress_accessor_t & operator=(
1432 const scenario_in_progress_accessor_t & ) = delete;
1433
1434 scenario_in_progress_accessor_t(
1435 scenario_in_progress_accessor_t && ) = delete;
1436 scenario_in_progress_accessor_t & operator=(
1437 scenario_in_progress_accessor_t && ) = delete;
1438
1439 public :
1440 abstract_scenario_t &
scenario() const1441 scenario() const noexcept { return m_scenario.get(); }
1442 };
1443
1444 /*!
1445 * \brief An interface of testing scenario.
1446 *
1447 * Please note that this type is a part of SObjectizer implementation
1448 * and is subject of changes in upcoming version. Do not use it in your
1449 * code, there is scenario_proxy_t call intended to be used by
1450 * end-users.
1451 *
1452 * This class is described in a public header-file in the current
1453 * version of SObjectizer. This is just to simplify implementation of
1454 * some testing-related stuff. The definition of that class can be move
1455 * to another file in future versions of SObjectizer without any
1456 * previous notice.
1457 *
1458 * \since
1459 * v.5.5.24
1460 */
1461 class abstract_scenario_t
1462 {
1463 protected :
1464 //! Helper method for creation of scenario_in_progress_accessor instance.
1465 [[nodiscard]]
1466 scenario_in_progress_accessor_t
make_accessor()1467 make_accessor() noexcept
1468 {
1469 return { outliving_mutable(*this) };
1470 }
1471
1472 public :
1473 /*!
1474 * \brief Type of token returned by pre-event-handler hook.
1475 *
1476 * Object of that type should be stored and then it should
1477 * be passed to abstract_scenario_t::post_handler_hook() method.
1478 *
1479 * A token can be valid. It means that token holds an actual
1480 * pointer to some scenario step.
1481 *
1482 * Or token can be invalid. It means that there is no a valid
1483 * pointer to scenario step. In that case methods like
1484 * activated_step() and step_token() should not be called.
1485 *
1486 * \since v.5.5.24
1487 */
1488 class token_t final
1489 {
1490 abstract_scenario_step_t * m_activated_step;
1491 abstract_scenario_step_t::token_t m_step_token;
1492
1493 public :
token_t()1494 token_t()
1495 : m_activated_step( nullptr )
1496 {}
token_t(abstract_scenario_step_t * activated_step,abstract_scenario_step_t::token_t step_token)1497 token_t(
1498 abstract_scenario_step_t * activated_step,
1499 abstract_scenario_step_t::token_t step_token )
1500 : m_activated_step( activated_step )
1501 , m_step_token( step_token )
1502 {}
1503
1504 [[nodiscard]]
1505 bool
valid() const1506 valid() const noexcept
1507 {
1508 return nullptr != m_activated_step;
1509 }
1510
1511 [[nodiscard]]
1512 abstract_scenario_step_t &
activated_step() const1513 activated_step() const noexcept
1514 {
1515 return *m_activated_step;
1516 }
1517
1518 [[nodiscard]]
1519 abstract_scenario_step_t::token_t
step_token() const1520 step_token() const noexcept
1521 {
1522 return m_step_token;
1523 }
1524 };
1525
1526 public :
1527 abstract_scenario_t() = default;
1528
1529 abstract_scenario_t( const abstract_scenario_t & ) = delete;
1530 abstract_scenario_t & operator=( const abstract_scenario_t & ) = delete;
1531
1532 abstract_scenario_t( abstract_scenario_t && ) = delete;
1533 abstract_scenario_t & operator=( abstract_scenario_t && ) = delete;
1534
1535 virtual ~abstract_scenario_t() = default;
1536
1537 //! Create a new step and return proxy for it.
1538 [[nodiscard]]
1539 virtual step_definition_proxy_t
1540 define_step( nonempty_name_t step_name ) = 0;
1541
1542 //! Get the result of scenario execution.
1543 [[nodiscard]]
1544 virtual scenario_result_t
1545 result() const noexcept = 0;
1546
1547 //! Run the scenario until completion or for specific amount of time.
1548 virtual void
1549 run_for( std::chrono::steady_clock::duration run_time ) = 0;
1550
1551 //! Hook that should be called before invocation of event-handler.
1552 /*!
1553 * This hook should be called before invocation of any event-handler
1554 * for ordinary message, service request or enveloped message.
1555 *
1556 * The token returned should then be passed to post_handler_hook().
1557 */
1558 [[nodiscard]]
1559 virtual token_t
1560 pre_handler_hook(
1561 const incident_info_t & info ) noexcept = 0;
1562
1563 //! Hook that should be called just after completion of event-handler.
1564 /*!
1565 * This hook should be called just after completion of any event-handler
1566 * for ordinary message, service request or enveloped message.
1567 */
1568 virtual void
1569 post_handler_hook(
1570 //! Token returned by previous pre_handler_hook() call.
1571 token_t token ) noexcept = 0;
1572
1573 //! Hook that should be called if there is no event-handler for
1574 //! a message or service request.
1575 virtual void
1576 no_handler_hook(
1577 const incident_info_t & info ) noexcept = 0;
1578
1579 //! Store a name of an agent state in the scenario.
1580 /*!
1581 * Note this method can be accessed only when scenario object is locked.
1582 */
1583 virtual void
1584 store_state_name(
1585 const scenario_in_progress_accessor_t & /*accessor*/,
1586 const abstract_scenario_step_t & step,
1587 const std::string & tag,
1588 const std::string & state_name ) = 0;
1589
1590 //! Get the stored state name.
1591 /*!
1592 * Should be called only after completion of scenario.
1593 *
1594 * Will throw an exception if there is no stored state for
1595 * a pair of (\a step_name,\a tag).
1596 */
1597 [[nodiscard]]
1598 virtual std::string
1599 stored_state_name(
1600 const std::string & step_name,
1601 const std::string & tag ) const = 0;
1602 };
1603
1604 /*!
1605 * \brief A helper operator to create a trigger for the specified agent.
1606 *
1607 * \since
1608 * v.5.5.24
1609 */
1610 template< incident_status_t Status >
1611 trigger_holder_t<Status>
operator &(const so_5::agent_t & agent,const trigger_source_t<Status> & src)1612 operator&(
1613 const so_5::agent_t & agent,
1614 const trigger_source_t<Status> & src )
1615 {
1616 const auto src_mbox_id = src.m_src_mbox_id ?
1617 *(src.m_src_mbox_id) : agent.so_direct_mbox()->id();
1618
1619 return {
1620 std::make_unique<trigger_t>(
1621 Status,
1622 agent,
1623 src.m_msg_type,
1624 src_mbox_id )
1625 };
1626 }
1627
1628 /*!
1629 * \brief A helper operator to create a tigger that stores the
1630 * name of the current agent's state.
1631 *
1632 * \since
1633 * v.5.5.24
1634 */
1635 inline trigger_holder_t<incident_status_t::handled>
operator &(trigger_holder_t<incident_status_t::handled> && old_holder,store_agent_state_name_t data)1636 operator&(
1637 trigger_holder_t<incident_status_t::handled> && old_holder,
1638 store_agent_state_name_t data )
1639 {
1640 auto trigger_ptr = old_holder.giveout_trigger();
1641 auto * target_agent = &(trigger_ptr->target_agent());
1642 trigger_ptr->set_completion(
1643 [data = std::move(data), target_agent](
1644 const trigger_completion_context_t & ctx ) noexcept
1645 {
1646 ctx.m_scenario_accessor.scenario().store_state_name(
1647 ctx.m_scenario_accessor,
1648 ctx.m_step,
1649 data.m_tag,
1650 target_agent->so_current_state().query_name() );
1651 } );
1652
1653 return { std::move(trigger_ptr) };
1654 }
1655
1656 } /* namespace details */
1657
1658 /*!
1659 * \brief A special wrapper around scenario object.
1660 *
1661 * The class scenario_proxy_t is a public interface to scenario object.
1662 * The actual scenario object is inside of testing_env_t instance and
1663 * access to it is provided via scenario_proxy_t wrapper.
1664 *
1665 * Usage example:
1666 * \code
1667 * using namespace so_5::experimental::testing;
1668 * TEST_CASE("some_case") {
1669 * testing_env_t env;
1670 *
1671 * so_5::agent_t * test_agent;
1672 * env.environment().introduce_coop([&](so_5::coop_t & coop) {
1673 * test_agent = coop.make_agent<some_agent>();
1674 * });
1675 *
1676 * env.scenario().define_step("one")
1677 * .impact<some_message>(*test_agent)
1678 * .when(*test_agent & reacts_to<some_message>());
1679 *
1680 * env.scenario().run_for(std::chrono::milliseconds(200));
1681 *
1682 * REQUIRE(completed() == env.scenario().result());
1683 * }
1684 * \endcode
1685 * Or in more conciseness form:
1686 * \code
1687 * using namespace so_5::experimental::testing;
1688 * TEST_CASE("some_case") {
1689 * testing_env_t env;
1690 *
1691 * so_5::agent_t * test_agent;
1692 * env.environment().introduce_coop([&](so_5::coop_t & coop) {
1693 * test_agent = coop.make_agent<some_agent>();
1694 * });
1695 *
1696 * auto scenario = env.scenario();
1697 *
1698 * scenario.define_step("one")
1699 * .impact<some_message>(*test_agent)
1700 * .when(*test_agent & reacts_to<some_message>());
1701 *
1702 * scenario.run_for(std::chrono::milliseconds(200));
1703 *
1704 * REQUIRE(completed() == scenario.result());
1705 * }
1706 * \endcode
1707 *
1708 * \note
1709 * scenario_proxy_t holds a reference to an object from testing_env_t
1710 * instance. It means that scenario_proxy shouldn't outlive the
1711 * corresponding testing_env_t object. For example this code will
1712 * lead to a dangling reference:
1713 * \code
1714 * so_5::experimental::testing::scenario_proxy_t get_scenario() {
1715 * so_5::experimental::testing::testing_env_t env;
1716 * return env.scenario(); // OOPS! A reference to destroyed object will be returned.
1717 * }
1718 * \endcode
1719 *
1720 * \since
1721 * v.5.5.24
1722 */
1723 class SO_5_TYPE scenario_proxy_t final
1724 {
1725 friend class testing_env_t;
1726
1727 private :
1728 outliving_reference_t< details::abstract_scenario_t > m_scenario;
1729
1730 scenario_proxy_t(
1731 outliving_reference_t< details::abstract_scenario_t > scenario );
1732
1733 public :
1734 //! Start definition of a new scenario's step.
1735 /*!
1736 * New steps can be defined until run_for() is called.
1737 * When the scenario is in progress (or is already
1738 * finished) then an attempt to call to define_step() will
1739 * lead to an exception.
1740 *
1741 * \return A special wrapper around a new step instance.
1742 * This wrapped should be used to define the step.
1743 *
1744 * \note
1745 * The value of \a step_name has to be unique, but the current
1746 * version of SObjectizer doesn't controll that.
1747 */
1748 [[nodiscard]]
1749 step_definition_proxy_t
1750 define_step( nonempty_name_t step_name );
1751
1752 //! Get the result of scenario execution.
1753 /*!
1754 * \note
1755 * This method is intended to be called only after the completion of
1756 * run_for() method.
1757 */
1758 [[nodiscard]]
1759 scenario_result_t
1760 result() const;
1761
1762 //! Runs the scenario for specified amount of time.
1763 /*!
1764 * This method does to things:
1765 * - unfreeze all agents registered in testing environment
1766 * up to this time;
1767 * - run scenario until scenario will be completed or
1768 * \a run_time elapsed.
1769 *
1770 * The result of scenario run can then be obtained by result()
1771 * method.
1772 *
1773 * Usage example:
1774 * \code
1775 * using namespace so_5::experimental::testing;
1776 * TEST_CASE("some_case") {
1777 * testing_env_t env; // Create a testing environment.
1778 *
1779 * // Introduce some agents.
1780 * env.environment().introduce_coop(...);
1781 *
1782 * // Do define the scenario for the test case.
1783 * auto scenario = env.scenario();
1784 * ...
1785 * // Run the scenario for at most 1 second.
1786 * scenario.run_for(std::chrono::seconds(1));
1787 * // Check the result of the scenario.
1788 * REQUIRE(completed() == scenario.result());
1789 * }
1790 * \endcode
1791 */
1792 void
1793 run_for( std::chrono::steady_clock::duration run_time );
1794
1795 //! Try to get stored name of an agent's state.
1796 /*!
1797 * This method allows to get state name stored by
1798 * store_state_name() trigger. For example:
1799 * \code
1800 * using namespace so_5::experimental::testing;
1801 * TEST_CASE("some_case") {
1802 * testing_env_t env;
1803 *
1804 * so_5::agent_t * test_agent;
1805 * env.environment().introduce_coop([&](so_5::coop_t & coop) {
1806 * test_agent = coop.make_agent<some_agent_type>(...);
1807 * });
1808 *
1809 * env.scenario().define_step("one")
1810 * .impact<some_message>(*test_agent, ...)
1811 * .when(*test_agent & reacts_to<some_message>()
1812 * & store_state_name("my_agent"));
1813 * ...
1814 * env.scenario().run_for(std::chrono::seconds(1));
1815 *
1816 * REQUIRE(completed() == env.scenario().result());
1817 *
1818 * REQUIRE("some_state" == env.scenario().stored_state_name("one", "my_agent"));
1819 * }
1820 * \endcode
1821 *
1822 * \attention
1823 * This method can be called only after completion of the scenario.
1824 * Otherwise an instance of so_5::exception_t will be thrown.
1825 *
1826 * \return Name of stored state. If there is no stored name for
1827 * a pair of (\a step_name, \a tag) then an instance of
1828 * so_5::exception_t will be thrown.
1829 */
1830 [[nodiscard]]
1831 std::string
1832 stored_state_name(
1833 const std::string & step_name,
1834 const std::string & tag ) const;
1835 };
1836
1837 /*!
1838 * \brief A special testing environment that should be used for
1839 * testing of agents.
1840 *
1841 * To allow testing of agent a special environment is necessary.
1842 * That environment creates special hooks those control event
1843 * handling. The class testing_env_t is an implementation of that
1844 * special environment.
1845 *
1846 * To create a test-case for some agent (or even several agents)
1847 * is it necessary to create an instance of testing_env_t class:
1848 * \code
1849 * using namespace so_5::experimental::testing;
1850 * TEST_CASE("some_case") {
1851 * testing_env_t env;
1852 * ...
1853 * }
1854 * \endcode
1855 * An instance of testing_env_t creates and launches SObjectizer
1856 * Environment in the constructor. That Environment will be shut down
1857 * in the destructor of testing_env_t instance automatically.
1858 * It is possible to shut down SObjectizer Environment manually
1859 * by using stop(), join(), stop_then_join() methods
1860 * (they play the same role as the corresponding methods of
1861 * so_5::wrapped_env_t class).
1862 *
1863 * The testing_env_t instance already has an instance of scenario
1864 * object inside. Access to that object can be obtained via
1865 * scenario() method:
1866 * \code
1867 * using namespace so_5::experimental::testing;
1868 * TEST_CASE("some_case") {
1869 * testing_env_t env;
1870 * ...
1871 * env.scenario().define_step("one")...;
1872 * ...
1873 * // Or, for conciseness:
1874 * auto scenario = env.scenario();
1875 * scenario.define_step("two")...;
1876 * ...
1877 * }
1878 * \endcode
1879 *
1880 * Please note that this is a special environment. One important
1881 * feature of it is a behaviour of agents created inside that
1882 * environment. All agents registered before call to
1883 * scenario_proxy_t::run_for() method will be in "frozen" mode:
1884 * they are present in the SObjectizer Environment, but they can't
1885 * receive any incoming messages (even so_5::agent_t::so_evt_start()
1886 * is not called for them).
1887 *
1888 * All registered agents will be unfreezed when scenario_proxy_t::run_for()
1889 * will be called (or if testing_env_t is stopped). For example:
1890 * \code
1891 * class hello final : public so_5::agent_t {
1892 * public:
1893 * using so_5::agent_t::agent_t;
1894 * void so_evt_start() override {
1895 * std::cout << "Hello, World!" << std::endl;
1896 * }
1897 * };
1898 * void testing_env_demo() {
1899 * so_5::experimental::testing::testing_env_t env;
1900 *
1901 * env.environment().introduce_coop([](so_5::coop_t & coop) {
1902 * coop.make_agent<hello>();
1903 * });
1904 *
1905 * std::cout << "Bye, bye!" << std::endl;
1906 *
1907 * env.scenario().run_for(std::chrono::milliseconds(100));
1908 * }
1909 * \endcode
1910 * In that case we will have the following output:
1911 \verbatim
1912 Bye, bye!
1913 Hello, World!
1914 \endverbatim
1915 *
1916 * \since
1917 * v.5.5.24
1918 */
1919 class SO_5_TYPE testing_env_t
1920 {
1921 public :
1922 //! Default constructor.
1923 /*!
1924 * \note
1925 * This constructor launches SObjectizer Environment with
1926 * default parameters.
1927 */
1928 testing_env_t();
1929 //! A constructor that allows to tune environment's parameters.
1930 /*!
1931 * Usage example:
1932 * \code
1933 * using namespace so_5::experimental::testing;
1934 * testing_env_t env{
1935 * [](so_5::environment_params_t & params) {
1936 * // Turn message delivery tracing on.
1937 * params.message_delivery_tracer(
1938 * so_5::msg_tracing::std_cout_tracer());
1939 * }
1940 * };
1941 * \endcode
1942 *
1943 * \note
1944 * The testing_env_t class can change some values in environment_params_t
1945 * after the return from \a env_params_tuner.
1946 */
1947 testing_env_t(
1948 //! Function for environment's params tuning.
1949 so_5::generic_simple_so_env_params_tuner_t env_params_tuner );
1950 //! A constructor that receives already constructed
1951 //! environment parameters.
1952 /*!
1953 * Usage example:
1954 * \code
1955 * so_5::environment_params_t make_params() {
1956 * so_5::environment_params_t params;
1957 * ...
1958 * params.message_delivery_tracer(
1959 * so_5::msg_tracing::std_cout_tracer());
1960 * ...
1961 * return params;
1962 * }
1963 * ...
1964 * TEST_CASE("some_case") {
1965 * using namespace so_5::experimental::testing;
1966 * testing_env_t env{ make_params() };
1967 * ...
1968 * }
1969 * \endcode
1970 *
1971 * \note
1972 * The testing_env_t class can change some values in environment_params_t
1973 * before launching a SObjectizer Environment.
1974 */
1975 testing_env_t(
1976 environment_params_t && env_params );
1977 ~testing_env_t();
1978
1979 //! Access to wrapped environment.
1980 environment_t &
1981 environment() const;
1982
1983 //! Send stop signal to environment.
1984 /*!
1985 * Please note that stop() just sends a shut down signal to the
1986 * SObjectizer Environment, but doesn't wait the completion of
1987 * the Environment. If you calls stop() method you have to
1988 * call join() to ensure that Environment is shut down.
1989 */
1990 void
1991 stop();
1992
1993 //! Wait for complete finish of environment's work.
1994 void
1995 join();
1996
1997 //! Send stop signal and wait for complete finish of environment's work.
1998 void
1999 stop_then_join();
2000
2001 //! Access to the associated scenario.
2002 [[nodiscard]]
2003 scenario_proxy_t
2004 scenario() noexcept;
2005
2006 struct internals_t;
2007
2008 private :
2009 std::unique_ptr< internals_t > m_internals;
2010
2011 wrapped_env_t m_sobjectizer;
2012
2013 void
2014 tune_environment_on_start( environment_t & env );
2015
2016 void
2017 wait_init_completion();
2018 };
2019
2020 } /* namespace v1 */
2021
2022 } /* namespace testing */
2023
2024 } /* namespace experimental */
2025
2026 } /* namespace so_5 */
2027
2028 #if defined( SO_5_MSVC )
2029 #pragma warning(pop)
2030 #endif
2031
2032