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