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  }