github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/cluster_test.go (about)

     1  package cluster
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/asynkron/protoactor-go/actor"
    11  	"github.com/asynkron/protoactor-go/remote"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  // inmemoryProvider use for test
    16  type inmemoryProvider struct {
    17  	cluster *Cluster
    18  	members map[string]*Member
    19  	self    *Member
    20  }
    21  
    22  func newInmemoryProvider() *inmemoryProvider {
    23  	return &inmemoryProvider{members: map[string]*Member{}}
    24  }
    25  
    26  func (p *inmemoryProvider) init(c *Cluster) error {
    27  	name := c.Config.Name
    28  	host, port, err := c.ActorSystem.GetHostPort()
    29  	if err != nil {
    30  		return err
    31  	}
    32  	p.cluster = c
    33  	p.self = &Member{
    34  		Host:  host,
    35  		Port:  int32(port),
    36  		Id:    fmt.Sprintf("%s@%s:%d", name, host, port),
    37  		Kinds: c.GetClusterKinds(),
    38  	}
    39  
    40  	return nil
    41  }
    42  
    43  func (p *inmemoryProvider) publishClusterTopologyEvent() {
    44  	var members Members
    45  	for _, m := range p.members {
    46  		members = append(members, m)
    47  	}
    48  
    49  	res := members
    50  
    51  	p.cluster.MemberList.UpdateClusterTopology(res)
    52  	// p.cluster.ActorSystem.EventStream.Publish(res)
    53  }
    54  
    55  func (p *inmemoryProvider) StartMember(c *Cluster) error {
    56  	err := p.init(c)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	p.members[p.self.Id] = p.self
    61  	p.publishClusterTopologyEvent()
    62  	return nil
    63  }
    64  
    65  func (p *inmemoryProvider) StartClient(c *Cluster) error {
    66  	err := p.init(c)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	p.publishClusterTopologyEvent()
    71  	return nil
    72  }
    73  
    74  func (p *inmemoryProvider) Shutdown(graceful bool) error {
    75  	delete(p.members, p.self.Id)
    76  
    77  	return nil
    78  }
    79  
    80  type fakeIdentityLookup struct {
    81  	m sync.Map
    82  }
    83  
    84  func (l *fakeIdentityLookup) Get(identity *ClusterIdentity) *actor.PID {
    85  	if val, ok := l.m.Load(identity.Identity); ok {
    86  		return val.(*actor.PID)
    87  	} else {
    88  		// pid := actor.NewPID("127.0.0.1", fmt.Sprintf("%s/%s", identity.Kind, identity.Identity))
    89  		// l.m.Store(identity.Identity, pid)
    90  		// return pid
    91  	}
    92  	return nil
    93  }
    94  
    95  func (l *fakeIdentityLookup) RemovePid(identity *ClusterIdentity, pid *actor.PID) {
    96  	if existPid := l.Get(identity); existPid.Equal(pid) {
    97  		l.m.Delete(identity.Identity)
    98  	}
    99  }
   100  
   101  func (lu *fakeIdentityLookup) Setup(cluster *Cluster, kinds []string, isClient bool) {
   102  }
   103  
   104  func (lu *fakeIdentityLookup) Shutdown() {
   105  }
   106  
   107  func newClusterForTest(name string, cp ClusterProvider, opts ...ConfigOption) *Cluster {
   108  	system := actor.NewActorSystem()
   109  	lookup := fakeIdentityLookup{}
   110  	cfg := Configure(name, cp, &lookup, remote.Configure("127.0.0.1", 0), opts...)
   111  	c := New(system, cfg)
   112  
   113  	c.MemberList = NewMemberList(c)
   114  	c.Config.RequestTimeoutTime = 1 * time.Second
   115  	c.Remote = remote.NewRemote(system, c.Config.RemoteConfig)
   116  	c.IdentityLookup = &lookup
   117  	return c
   118  }
   119  
   120  func TestCluster_Call(t *testing.T) {
   121  	assert := assert.New(t)
   122  
   123  	members := Members{
   124  		{
   125  			Id:    "1",
   126  			Host:  "nonhost",
   127  			Port:  -1,
   128  			Kinds: []string{"kind"},
   129  		},
   130  	}
   131  	c := newClusterForTest("mycluster", nil)
   132  	c.MemberList.UpdateClusterTopology(members)
   133  	t.Run("invalid kind", func(t *testing.T) {
   134  		msg := struct{}{}
   135  		resp, err := c.Request("name", "nonkind", &msg)
   136  		assert.ErrorContains(err, "max retries")
   137  		assert.Nil(resp)
   138  	})
   139  
   140  	testProps := actor.PropsFromFunc(
   141  		func(context actor.Context) {
   142  			switch msg := context.Message().(type) {
   143  			case *struct{ Code int }:
   144  				msg.Code++
   145  				context.Respond(msg)
   146  			}
   147  		})
   148  	pid := c.ActorSystem.Root.Spawn(testProps)
   149  	assert.NotNil(pid)
   150  	c.PidCache.Set("name", "kind", pid)
   151  	t.Run("normal", func(t *testing.T) {
   152  		msg := struct{ Code int }{9527}
   153  		resp, err := c.Request("name", "kind", &msg)
   154  		assert.NoError(err)
   155  		assert.Equal(&struct{ Code int }{9528}, resp)
   156  	})
   157  
   158  	t.Run("timeout", func(t *testing.T) {
   159  		msg := struct{}{}
   160  		resp, err := c.Request("name", "kind", &msg, WithTimeout(time.Millisecond))
   161  		assert.ErrorIs(err, context.DeadlineExceeded)
   162  		assert.Nil(resp)
   163  	})
   164  }
   165  
   166  func TestCluster_Get(t *testing.T) {
   167  	t.Skipf("Maintaining")
   168  	cp := newInmemoryProvider()
   169  	kind := NewKind("kind", actor.PropsFromFunc(func(ctx actor.Context) {
   170  		switch msg := ctx.Message().(type) {
   171  		case *actor.Started:
   172  			_ = msg
   173  		}
   174  	}))
   175  	c := newClusterForTest("mycluster", cp, WithKinds(kind))
   176  	c.StartMember()
   177  	cp.publishClusterTopologyEvent()
   178  	t.Run("invalid kind", func(t *testing.T) {
   179  		assert := assert.New(t)
   180  		assert.Equal(1, c.MemberList.Length())
   181  		pid := c.Get("name", "nonkind")
   182  		assert.Nil(pid)
   183  	})
   184  
   185  	t.Run("ok", func(t *testing.T) {
   186  		assert := assert.New(t)
   187  		pid := c.Get("name", "kind")
   188  		assert.NotNil(pid)
   189  	})
   190  }