github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/actor/child_test.go (about) 1 package actor 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 ) 10 11 type ( 12 CreateChildMessage struct{} 13 GetChildCountRequest struct{} 14 GetChildCountResponse struct{ ChildCount int } 15 CreateChildActor struct{} 16 ) 17 18 func (*CreateChildActor) Receive(context Context) { 19 switch context.Message().(type) { 20 case CreateChildMessage: 21 context.Spawn(PropsFromProducer(NewBlackHoleActor)) 22 case GetChildCountRequest: 23 reply := GetChildCountResponse{ChildCount: len(context.Children())} 24 context.Respond(reply) 25 } 26 } 27 28 func NewCreateChildActor() Actor { 29 return &CreateChildActor{} 30 } 31 32 func TestActorCanCreateChildren(t *testing.T) { 33 a := rootContext.Spawn(PropsFromProducer(NewCreateChildActor)) 34 defer rootContext.Stop(a) 35 expected := 10 36 for i := 0; i < expected; i++ { 37 rootContext.Send(a, CreateChildMessage{}) 38 } 39 fut := rootContext.RequestFuture(a, GetChildCountRequest{}, testTimeout) 40 response := assertFutureSuccess(fut, t) 41 assert.Equal(t, expected, response.(GetChildCountResponse).ChildCount) 42 } 43 44 type CreateChildThenStopActor struct { 45 replyTo *PID 46 } 47 48 type GetChildCountMessage2 struct { 49 ReplyDirectly *PID 50 ReplyAfterStop *PID 51 } 52 53 func (state *CreateChildThenStopActor) Receive(context Context) { 54 switch msg := context.Message().(type) { 55 case CreateChildMessage: 56 context.Spawn(PropsFromProducer(NewBlackHoleActor)) 57 case GetChildCountMessage2: 58 context.Send(msg.ReplyDirectly, true) 59 state.replyTo = msg.ReplyAfterStop 60 case *Stopped: 61 reply := GetChildCountResponse{ChildCount: len(context.Children())} 62 context.Send(state.replyTo, reply) 63 } 64 } 65 66 func NewCreateChildThenStopActor() Actor { 67 return &CreateChildThenStopActor{} 68 } 69 70 func TestActorCanStopChildren(t *testing.T) { 71 actor := rootContext.Spawn(PropsFromProducer(NewCreateChildThenStopActor)) 72 count := 10 73 for i := 0; i < count; i++ { 74 rootContext.Send(actor, CreateChildMessage{}) 75 } 76 77 future := NewFuture(system, testTimeout) 78 future2 := NewFuture(system, testTimeout) 79 rootContext.Send(actor, GetChildCountMessage2{ReplyDirectly: future.PID(), ReplyAfterStop: future2.PID()}) 80 81 // wait for the actor to reply to the first responsePID 82 assertFutureSuccess(future, t) 83 84 // then send a stop command 85 rootContext.Stop(actor) 86 87 // wait for the actor to stop and get the result from the stopped handler 88 response := assertFutureSuccess(future2, t) 89 // we should have 0 children when the actor is stopped 90 assert.Equal(t, 0, response.(GetChildCountResponse).ChildCount) 91 } 92 93 func TestActorReceivesTerminatedFromWatched(t *testing.T) { 94 child := rootContext.Spawn(PropsFromFunc(nullReceive)) 95 future := NewFuture(system, testTimeout) 96 var wg sync.WaitGroup 97 wg.Add(1) 98 99 var r ReceiveFunc = func(c Context) { 100 switch msg := c.Message().(type) { 101 case *Started: 102 c.Watch(child) 103 wg.Done() 104 105 case *Terminated: 106 ac := c.(*actorContext) 107 if msg.Who.Equal(child) && ac.ensureExtras().watchers.Empty() { 108 c.Send(future.PID(), true) 109 } 110 } 111 } 112 113 rootContext.Spawn(PropsFromFunc(r)) 114 wg.Wait() 115 rootContext.Stop(child) 116 117 assertFutureSuccess(future, t) 118 } 119 120 func TestFutureDoesTimeout(t *testing.T) { 121 pid := rootContext.Spawn(PropsFromFunc(nullReceive)) 122 _, err := rootContext.RequestFuture(pid, "", time.Millisecond).Result() 123 assert.EqualError(t, err, ErrTimeout.Error()) 124 } 125 126 func TestFutureDoesNotTimeout(t *testing.T) { 127 var r ReceiveFunc = func(c Context) { 128 if _, ok := c.Message().(string); !ok { 129 return 130 } 131 132 time.Sleep(50 * time.Millisecond) 133 c.Respond("foo") 134 } 135 pid := rootContext.Spawn(PropsFromFunc(r)) 136 reply, err := rootContext.RequestFuture(pid, "", 2*time.Second).Result() 137 assert.NoError(t, err) 138 assert.Equal(t, "foo", reply) 139 }