1 module tagion.testbench.actor.message;
2 
3 import core.time;
4 import std.format : format;
5 import std.meta;
6 import std.stdio;
7 import std.variant : Variant;
8 import tagion.actor.actor;
9 import tagion.testbench.actor.util;
10 import tagion.utils.pretend_safe_concurrency;
11 
12 // Default import list for bdd
13 import core.thread;
14 import std.typecons : Tuple;
15 import tagion.behaviour;
16 import tagion.hibon.Document;
17 import tagion.testbench.tools.Environment;
18 
19 enum feature = Feature(
20             "Actor messaging",
21             ["This feature should verify the message send between actors"]);
22 
23 alias FeatureContext = Tuple!(
24         MessageBetweenSupervisorAndChild, "MessageBetweenSupervisorAndChild",
25         SendMessageBetweenTwoChildren, "SendMessageBetweenTwoChildren",
26         FeatureGroup*, "result"
27 );
28 
29 enum supervisor_task_name = "supervisor";
30 enum child1_task_name = "ch1ld";
31 enum child2_task_name = "ch2ld";
32 
33 // Child actor
34 @safe
35 struct MyActor {
36     int counter = 0;
37     void increase(Msg!"increase") {
38         counter++;
39         sendOwner(Msg!"response"(), counter);
40     }
41 
42     void decrease(Msg!"decrease") {
43         counter--;
44         sendOwner(Msg!"response"(), counter);
45     }
46 
47     void relay(Msg!"relay", string to, string message) {
48         locate(to).send(Msg!"relay"(), supervisor_task_name, message);
49     }
50 
51     void task() nothrow {
52         run(&increase, &decrease, &relay);
53     }
54 }
55 
56 @safe
57 struct MySuperActor {
58     ActorHandle child1Handle;
59     ActorHandle child2Handle;
60 
61     void receiveStatus(Msg!"response", int status) {
62         sendOwner(status);
63     }
64 
65     void roundtrip(Msg!"roundtrip", string message) {
66         child1Handle.send(Msg!"relay"(), child2_task_name, message);
67     }
68 
69     void relay(Msg!"relay", string _, string message) {
70         sendOwner(message);
71     }
72 
73     void task() nothrow {
74         child1Handle = spawn!MyActor(child1_task_name);
75         child2Handle = spawn!MyActor(child2_task_name);
76 
77         waitforChildren(Ctrl.ALIVE);
78         run(&receiveStatus, &roundtrip, &relay);
79     }
80 
81 }
82 
83 @safe @Scenario("Message between supervisor and child",
84         [])
85 class MessageBetweenSupervisorAndChild {
86     ActorHandle supervisorHandle;
87     ActorHandle childHandleUno;
88     ActorHandle childHandleDos;
89 
90     @Given("a supervisor #super and two child actors #child1 and #child2")
91     Document actorsChild1AndChild2() {
92         supervisorHandle = spawn!MySuperActor(supervisor_task_name);
93 
94         check(waitforChildren(Ctrl.ALIVE), "Supervisor did not alive");
95         check(supervisorHandle.tid !is Tid.init, "Supervisor thread is not running");
96 
97         return result_ok;
98     }
99 
100     @When("the #super has started the #child1 and #child2")
101     Document theChild1AndChild2() {
102         // The supervisor should only send alive when it has receive alive from the children.
103         // we assign the child handles
104         childHandleUno = ActorHandle(child1_task_name);
105         childHandleDos = ActorHandle(child2_task_name);
106 
107         return result_ok;
108     }
109 
110     @Then("send a message to #child1")
111     Document aMessageToChild1() {
112         check(locate(child1_task_name) !is Tid.init, "Child 1 thread is not running");
113         childHandleUno.send(Msg!"increase"());
114 
115         return result_ok;
116     }
117 
118     @Then("send this message back from #child1 to #super")
119     Document fromChild1ToSuper() {
120         check(receiveOnlyTimeout!int == 1, "Child 1 did not send back the expected value of 1");
121 
122         return result_ok;
123     }
124 
125     @Then("send a message to #child2")
126     Document aMessageToChild2() {
127         childHandleDos.send(Msg!"decrease"());
128         return result_ok;
129     }
130 
131     @Then("send thus message back from #child2 to #super")
132     Document fromChild2ToSuper() {
133         check(receiveOnlyTimeout!int == -1, "Child 2 did not send back the expected value of 1");
134         return result_ok;
135     }
136 
137     @Then("stop the #super")
138     Document stopTheSuper() {
139         supervisorHandle.send(Sig.STOP);
140         check(waitforChildren(Ctrl.END), "Supervisor did not end");
141 
142         return result_ok;
143     }
144 
145 }
146 
147 @safe @Scenario("send message between two children",
148         [])
149 class SendMessageBetweenTwoChildren {
150     ActorHandle supervisorHandle;
151     ActorHandle childHandleUno;
152     ActorHandle childHandleDos;
153 
154     @Given("a supervisor #super and two child actors #child1 and #child2")
155     Document actorsChild1AndChild2() {
156         supervisorHandle = spawn!MySuperActor(supervisor_task_name);
157         check(waitforChildren(Ctrl.ALIVE), "Supervisor is not alive");
158         check(supervisorHandle.tid !is Tid.init, "Supervisor thread is not running");
159 
160         return result_ok;
161     }
162 
163     @When("the #super has started the #child1 and #child2")
164     Document theChild1AndChild2() {
165         // The supervisor should only send alive when it has receive alive from the children.
166         // we assign the child handles
167         childHandleUno = ActorHandle(child1_task_name);
168         childHandleDos = ActorHandle(child2_task_name);
169 
170         return result_ok;
171     }
172 
173     @When("send a message from #super to #child1 and from #child1 to #child2 and back to the #super")
174     Document backToTheSuper() {
175 
176         enum message = "Hello Tagion";
177         supervisorHandle.send(Msg!"roundtrip"(), message);
178         check(receiveOnlyTimeout!string == message, "Did not get the same message back");
179 
180         return result_ok;
181     }
182 
183     @Then("stop the #super")
184     Document stopTheSuper() {
185         supervisorHandle.send(Sig.STOP);
186         check(waitforChildren(Ctrl.END), "Supervisor did not end ");
187         return result_ok;
188     }
189 }