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 }