package internal import ( "errors" "sort" "strconv" "testing" "zero/core/logx" "zero/core/mathx" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) func init() { logx.Disable() } func TestRoundRobin_addConn(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return errors.New("error") }, false) assert.Nil(t, b.AddConn(KV{ Key: "thekey1", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey1", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey1"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey1": "thevalue", }, b.mapping) assert.Nil(t, b.AddConn(KV{ Key: "thekey2", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey1", conn: mockConn{server: "thevalue"}, }, { key: "thekey2", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey1", "thekey2"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey1": "thevalue", "thekey2": "thevalue", }, b.mapping) assert.False(t, b.IsEmpty()) b.RemoveKey("thekey1") assert.EqualValues(t, []serverConn{ { key: "thekey2", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey2"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey2": "thevalue", }, b.mapping) assert.False(t, b.IsEmpty()) b.RemoveKey("thekey2") assert.EqualValues(t, []serverConn{}, b.conns) assert.EqualValues(t, map[string][]string{}, b.servers) assert.EqualValues(t, map[string]string{}, b.mapping) assert.True(t, b.IsEmpty()) } func TestRoundRobin_addConnExclusive(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return nil }, true) assert.Nil(t, b.AddConn(KV{ Key: "thekey1", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey1", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey1"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey1": "thevalue", }, b.mapping) assert.Nil(t, b.AddConn(KV{ Key: "thekey2", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey2", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey2"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey2": "thevalue", }, b.mapping) assert.False(t, b.IsEmpty()) b.RemoveKey("thekey1") b.RemoveKey("thekey2") assert.EqualValues(t, []serverConn{}, b.conns) assert.EqualValues(t, map[string][]string{}, b.servers) assert.EqualValues(t, map[string]string{}, b.mapping) assert.True(t, b.IsEmpty()) } func TestRoundRobin_addConnDupExclusive(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return errors.New("error") }, true) assert.Nil(t, b.AddConn(KV{ Key: "thekey1", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey1", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "thevalue": {"thekey1"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey1": "thevalue", }, b.mapping) assert.Nil(t, b.AddConn(KV{ Key: "thekey", Val: "anothervalue", })) assert.Nil(t, b.AddConn(KV{ Key: "thekey1", Val: "thevalue", })) assert.EqualValues(t, []serverConn{ { key: "thekey", conn: mockConn{server: "anothervalue"}, }, { key: "thekey1", conn: mockConn{server: "thevalue"}, }, }, b.conns) assert.EqualValues(t, map[string][]string{ "anothervalue": {"thekey"}, "thevalue": {"thekey1"}, }, b.servers) assert.EqualValues(t, map[string]string{ "thekey": "anothervalue", "thekey1": "thevalue", }, b.mapping) assert.False(t, b.IsEmpty()) b.RemoveKey("thekey") b.RemoveKey("thekey1") assert.EqualValues(t, []serverConn{}, b.conns) assert.EqualValues(t, map[string][]string{}, b.servers) assert.EqualValues(t, map[string]string{}, b.mapping) assert.True(t, b.IsEmpty()) } func TestRoundRobin_addConnError(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return nil, errors.New("error") }, func(server string, conn interface{}) error { return nil }, true) assert.NotNil(t, b.AddConn(KV{ Key: "thekey1", Val: "thevalue", })) assert.Nil(t, b.conns) assert.EqualValues(t, map[string][]string{}, b.servers) assert.EqualValues(t, map[string]string{}, b.mapping) } func TestRoundRobin_initialize(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return nil }, true) for i := 0; i < 100; i++ { assert.Nil(t, b.AddConn(KV{ Key: "thekey/" + strconv.Itoa(i), Val: "thevalue/" + strconv.Itoa(i), })) } m := make(map[int]int) const total = 1000 for i := 0; i < total; i++ { b.initialize() m[b.index]++ } mi := make(map[interface{}]int, len(m)) for k, v := range m { mi[k] = v } entropy := mathx.CalcEntropy(mi, total) assert.True(t, entropy > .95) } func TestRoundRobin_next(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return errors.New("error") }, true) const size = 100 for i := 0; i < size; i++ { assert.Nil(t, b.AddConn(KV{ Key: "thekey/" + strconv.Itoa(i), Val: "thevalue/" + strconv.Itoa(i), })) } m := make(map[interface{}]int) const total = 10000 for i := 0; i < total; i++ { val, ok := b.Next() assert.True(t, ok) m[val]++ } entropy := mathx.CalcEntropy(m, total) assert.Equal(t, size, len(m)) assert.True(t, entropy > .95) for i := 0; i < size; i++ { b.RemoveKey("thekey/" + strconv.Itoa(i)) } _, ok := b.Next() assert.False(t, ok) } func TestRoundRobinBalancer_Listener(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return nil }, true) assert.Nil(t, b.AddConn(KV{ Key: "key1", Val: "val1", })) assert.Nil(t, b.AddConn(KV{ Key: "key2", Val: "val2", })) listener := NewMockListener(ctrl) listener.EXPECT().OnUpdate(gomock.Any(), gomock.Any(), "key2").Do(func(keys, vals, _ interface{}) { sort.Strings(vals.([]string)) sort.Strings(keys.([]string)) assert.EqualValues(t, []string{"key1", "key2"}, keys) assert.EqualValues(t, []string{"val1", "val2"}, vals) }) b.setListener(listener) b.notify("key2") } func TestRoundRobinBalancer_remove(t *testing.T) { b := NewRoundRobinBalancer(func(server string) (interface{}, error) { return mockConn{ server: server, }, nil }, func(server string, conn interface{}) error { return nil }, true) assert.Nil(t, b.handlePrevious(nil, "any")) _, ok := b.doRemoveKv("any") assert.True(t, ok) }