github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/actor/actor_context_test.go (about) 1 package actor 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 ) 11 12 func FuzzSpawnNamed(f *testing.F) { 13 f.Add("parent", "child") 14 15 f.Fuzz(func(t *testing.T, parentName string, childName string) { 16 combined := parentName + "/" + childName 17 18 pid, _ := spawnMockProcess(parentName) 19 20 defer removeMockProcess(pid) 21 22 props := &Props{ 23 spawner: func(actorSystem *ActorSystem, id string, _ *Props, _ SpawnerContext) (*PID, error) { 24 assert.Equal(t, combined, id) 25 26 return NewPID(actorSystem.Address(), id), nil 27 }, 28 } 29 30 parent := &actorContext{self: NewPID(localAddress, parentName), props: props, actorSystem: system} 31 child, err := parent.SpawnNamed(props, childName) 32 assert.NoError(t, err) 33 assert.Equal(t, parent.Children()[0], child) 34 }) 35 } 36 37 // TestActorContext_Stop verifies if context is stopping and receives a Watch message, it should 38 // immediately respond with a Terminated message. 39 func TestActorContext_Stop(t *testing.T) { 40 t.Parallel() 41 42 pid, p := spawnMockProcess("foo") 43 defer removeMockProcess(pid) 44 45 other, o := spawnMockProcess("watcher") 46 defer removeMockProcess(other) 47 48 o.On("SendSystemMessage", other, &Terminated{Who: pid}) 49 50 props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy())) 51 lc := newActorContext(system, props, nil) 52 lc.self = pid 53 lc.InvokeSystemMessage(&Stop{}) 54 lc.InvokeSystemMessage(&Watch{Watcher: other}) 55 56 p.AssertExpectations(t) 57 o.AssertExpectations(t) 58 } 59 60 func TestActorContext_SendMessage_WithSenderMiddleware(t *testing.T) { 61 t.Parallel() 62 63 var wg sync.WaitGroup 64 65 wg.Add(1) 66 67 // Define a local context with no-op sender middleware 68 mw := func(next SenderFunc) SenderFunc { 69 return func(ctx SenderContext, target *PID, envelope *MessageEnvelope) { 70 next(ctx, target, envelope) 71 } 72 } 73 74 props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy()), WithSenderMiddleware(mw)) 75 ctx := newActorContext(system, props, nil) 76 77 // Define a receiver to which the local context will send a message 78 var counter int 79 80 receiver := rootContext.Spawn(PropsFromFunc(func(ctx Context) { 81 if _, ok := ctx.Message().(bool); ok { 82 counter++ 83 wg.Done() 84 } 85 })) 86 87 // Send a message with Tell 88 // Then wait a little to allow the receiver to process the message 89 // TODO: There should be a better way to wait. 90 timeout := 3 * time.Millisecond 91 92 ctx.Send(receiver, true) 93 wg.Wait() 94 assert.Equal(t, 1, counter) 95 96 // Send a message with Request 97 counter = 0 // Reset the counter 98 99 wg.Add(1) 100 ctx.Request(receiver, true) 101 wg.Wait() 102 assert.Equal(t, 1, counter) 103 104 // Send a message with RequestFuture 105 counter = 0 // Reset the counter 106 107 wg.Add(1) 108 109 _ = ctx.RequestFuture(receiver, true, timeout).Wait() 110 wg.Wait() 111 assert.Equal(t, 1, counter) 112 } 113 114 func BenchmarkActorContext_ProcessMessageNoMiddleware(b *testing.B) { 115 var m interface{} = 1 116 117 ctx := newActorContext(system, PropsFromFunc(nullReceive), nil) 118 for i := 0; i < b.N; i++ { 119 ctx.processMessage(m) 120 } 121 } 122 123 func TestActorContext_Respond(t *testing.T) { 124 t.Parallel() 125 126 var wg sync.WaitGroup 127 128 wg.Add(1) 129 130 // Defined a responder actor 131 // It simply echoes a received string. 132 responder := rootContext.Spawn(PropsFromFunc(func(ctx Context) { 133 if m, ok := ctx.Message().(string); ok { 134 ctx.Respond(fmt.Sprintf("Got a string: %v", m)) 135 } 136 })) 137 138 // Be prepared to catch a response that the responder will send to nil 139 var gotResponseToNil bool 140 141 deadLetterSubscriber := system.EventStream.Subscribe(func(msg interface{}) { 142 if deadLetter, ok := msg.(*DeadLetterEvent); ok { 143 if deadLetter.PID == nil { 144 gotResponseToNil = true 145 wg.Done() 146 } 147 } 148 }) 149 150 // Send a message to the responder using Request 151 // The responder should send something back. 152 timeout := 3 * time.Millisecond 153 res, err := rootContext.RequestFuture(responder, "hello", timeout).Result() 154 assert.Nil(t, err) 155 assert.NotNil(t, res) 156 157 resStr, ok := res.(string) 158 assert.True(t, ok) 159 assert.Equal(t, "Got a string: hello", resStr) 160 161 // Ensure that the responder did not send anything to nil 162 assert.False(t, gotResponseToNil) 163 164 // Send a message using Tell 165 rootContext.Send(responder, "hello") 166 167 // Ensure that the responder actually send something to nil 168 wg.Wait() 169 assert.True(t, gotResponseToNil) 170 171 // Cleanup 172 system.EventStream.Unsubscribe(deadLetterSubscriber) 173 } 174 175 func TestActorContext_Forward(t *testing.T) { 176 t.Parallel() 177 // Defined a response actor 178 // It simply responds to the string message 179 responder := rootContext.Spawn(PropsFromFunc(func(ctx Context) { 180 if m, ok := ctx.Message().(string); ok { 181 ctx.Respond(fmt.Sprintf("Got a string: %v", m)) 182 } 183 })) 184 185 // Defined a forwarder actor 186 // It simply forward the string message to responder 187 forwarder := rootContext.Spawn(PropsFromFunc(func(ctx Context) { 188 if _, ok := ctx.Message().(string); ok { 189 ctx.Forward(responder) 190 } 191 })) 192 193 // Send a message to the responder using Request 194 // The responder should send something back. 195 timeout := 3 * time.Millisecond 196 res, err := rootContext.RequestFuture(forwarder, "hello", timeout).Result() 197 assert.Nil(t, err) 198 assert.NotNil(t, res) 199 200 resStr, ok := res.(string) 201 assert.True(t, ok) 202 assert.Equal(t, "Got a string: hello", resStr) 203 } 204 205 func BenchmarkActorContext_ProcessMessageWithMiddleware(b *testing.B) { 206 var m interface{} = 1 207 208 fn := func(next ReceiverFunc) ReceiverFunc { 209 return func(ctx ReceiverContext, env *MessageEnvelope) { 210 next(ctx, env) 211 } 212 } 213 214 props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy()), WithReceiverMiddleware(fn)) 215 ctx := newActorContext(system, props, nil) 216 217 for i := 0; i < b.N; i++ { 218 ctx.processMessage(m) 219 } 220 } 221 222 func benchmarkactorcontextSpawnwithmiddlewaren(n int, b *testing.B) { 223 middlewareFn := func(next SenderFunc) SenderFunc { 224 return func(ctx SenderContext, pid *PID, env *MessageEnvelope) { 225 next(ctx, pid, env) 226 } 227 } 228 229 props := PropsFromProducer(nullProducer) 230 for i := 0; i < n; i++ { 231 props = props.Configure(WithSenderMiddleware(middlewareFn)) 232 } 233 234 system := NewActorSystem() 235 parent := &actorContext{self: NewPID(localAddress, "foo"), props: props, actorSystem: system} 236 237 for i := 0; i < b.N; i++ { 238 parent.Spawn(props) 239 } 240 } 241 242 func BenchmarkActorContext_SpawnWithMiddleware0(b *testing.B) { 243 benchmarkactorcontextSpawnwithmiddlewaren(0, b) 244 } 245 246 func BenchmarkActorContext_SpawnWithMiddleware1(b *testing.B) { 247 benchmarkactorcontextSpawnwithmiddlewaren(1, b) 248 } 249 250 func BenchmarkActorContext_SpawnWithMiddleware2(b *testing.B) { 251 benchmarkactorcontextSpawnwithmiddlewaren(2, b) 252 } 253 254 func BenchmarkActorContext_SpawnWithMiddleware5(b *testing.B) { 255 benchmarkactorcontextSpawnwithmiddlewaren(5, b) 256 } 257 258 func TestActorContinueFutureInActor(t *testing.T) { 259 t.Parallel() 260 261 pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) { 262 if ctx.Message() == "request" { 263 ctx.Respond("done") 264 } 265 if ctx.Message() == "start" { 266 f := ctx.RequestFuture(ctx.Self(), "request", 5*time.Second) 267 ctx.ReenterAfter(f, func(res interface{}, err error) { 268 ctx.Respond(res) 269 }) 270 } 271 })) 272 res, err := rootContext.RequestFuture(pid, "start", time.Second).Result() 273 assert.NoError(t, err) 274 assert.Equal(t, "done", res) 275 } 276 277 type dummyAutoRespond struct{} 278 279 func (*dummyAutoRespond) GetAutoResponse(_ Context) interface{} { 280 return &dummyResponse{} 281 } 282 283 func TestActorContextAutoRespondMessage(t *testing.T) { 284 t.Parallel() 285 286 pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {})) 287 288 var msg AutoRespond = &dummyAutoRespond{} 289 290 res, err := rootContext.RequestFuture(pid, msg, 1*time.Second).Result() 291 assert.NoError(t, err) 292 assert.IsType(t, &dummyResponse{}, res) 293 } 294 295 func TestActorContextAutoRespondTouchedMessage(t *testing.T) { 296 t.Parallel() 297 298 pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {})) 299 300 var msg AutoRespond = &Touch{} 301 302 res, err := rootContext.RequestFuture(pid, msg, 1*time.Second).Result() 303 304 res2, _ := res.(*Touched) 305 306 assert.NoError(t, err) 307 assert.IsType(t, &Touched{}, res) 308 assert.True(t, res2.Who.Equal(pid)) 309 }