func TestRandomNoEndpoints(t *testing.T) { lb := loadbalancer.NewRandom(fixed.NewPublisher([]endpoint.Endpoint{}), 123) _, have := lb.Endpoint() if want := loadbalancer.ErrNoEndpoints; want != have { t.Errorf("want %q, have %q", want, have) } }
func TestRoundRobinDistribution(t *testing.T) { var ( ctx = context.Background() counts = []int{0, 0, 0} endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { counts[0]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[1]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[2]++; return struct{}{}, nil }, } ) lb := loadbalancer.NewRoundRobin(fixed.NewPublisher(endpoints)) for i, want := range [][]int{ {1, 0, 0}, {1, 1, 0}, {1, 1, 1}, {2, 1, 1}, {2, 2, 1}, {2, 2, 2}, {3, 2, 2}, } { e, err := lb.Endpoint() if err != nil { t.Fatal(err) } e(ctx, struct{}{}) if have := counts; !reflect.DeepEqual(want, have) { t.Fatalf("%d: want %v, have %v", i, want, have) } } }
func TestRandomDistribution(t *testing.T) { var ( n = 3 endpoints = make([]endpoint.Endpoint, n) counts = make([]int, n) seed = int64(123) ctx = context.Background() iterations = 100000 want = iterations / n tolerance = want / 100 // 1% ) for i := 0; i < n; i++ { i0 := i endpoints[i] = func(context.Context, interface{}) (interface{}, error) { counts[i0]++; return struct{}{}, nil } } lb := loadbalancer.NewRandom(fixed.NewPublisher(endpoints), seed) for i := 0; i < iterations; i++ { e, err := lb.Endpoint() if err != nil { t.Fatal(err) } e(ctx, struct{}{}) } for i, have := range counts { if math.Abs(float64(want-have)) > float64(tolerance) { t.Errorf("%d: want %d, have %d", i, want, have) } } }
func TestRetryMaxTotalFail(t *testing.T) { var ( endpoints = []endpoint.Endpoint{} // no endpoints p = fixed.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) retry = loadbalancer.Retry(999, time.Second, lb) // lots of retries ctx = context.Background() ) if _, err := retry(ctx, struct{}{}); err == nil { t.Errorf("expected error, got none") // should fail } }
// NewPublisher returns a static endpoint Publisher. func NewPublisher(instances []string, factory loadbalancer.Factory, logger log.Logger) Publisher { logger = log.NewContext(logger).With("component", "Fixed Publisher") endpoints := []endpoint.Endpoint{} for _, instance := range instances { e, err := factory(instance) if err != nil { _ = logger.Log("instance", instance, "err", err) continue } endpoints = append(endpoints, e) } return Publisher{fixed.NewPublisher(endpoints)} }
func TestFixed(t *testing.T) { var ( e1 = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } e2 = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } endpoints = []endpoint.Endpoint{e1, e2} ) p := fixed.NewPublisher(endpoints) have, err := p.Endpoints() if err != nil { t.Fatal(err) } if want := endpoints; !reflect.DeepEqual(want, have) { t.Fatalf("want %#+v, have %#+v", want, have) } }
func TestRetryMaxSuccess(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } retries = len(endpoints) // exactly enough retries p = fixed.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) ctx = context.Background() ) if _, err := loadbalancer.Retry(retries, time.Second, lb)(ctx, struct{}{}); err != nil { t.Error(err) } }
func TestRetryMaxPartialFail(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } retries = len(endpoints) - 1 // not quite enough retries p = fixed.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) ctx = context.Background() ) if _, err := loadbalancer.Retry(retries, time.Second, lb)(ctx, struct{}{}); err == nil { t.Errorf("expected error, got none") } }
func TestFixedReplace(t *testing.T) { p := fixed.NewPublisher([]endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, }) have, err := p.Endpoints() if err != nil { t.Fatal(err) } if want, have := 1, len(have); want != have { t.Fatalf("want %d, have %d", want, have) } p.Replace([]endpoint.Endpoint{}) have, err = p.Endpoints() if err != nil { t.Fatal(err) } if want, have := 0, len(have); want != have { t.Fatalf("want %d, have %d", want, have) } }
func TestRetryTimeout(t *testing.T) { var ( step = make(chan struct{}) e = func(context.Context, interface{}) (interface{}, error) { <-step; return struct{}{}, nil } timeout = time.Millisecond retry = loadbalancer.Retry(999, timeout, loadbalancer.NewRoundRobin(fixed.NewPublisher([]endpoint.Endpoint{e}))) errs = make(chan error, 1) invoke = func() { _, err := retry(context.Background(), struct{}{}); errs <- err } ) go func() { step <- struct{}{} }() // queue up a flush of the endpoint invoke() // invoke the endpoint and trigger the flush if err := <-errs; err != nil { // that should succeed t.Error(err) } go func() { time.Sleep(10 * timeout); step <- struct{}{} }() // a delayed flush invoke() // invoke the endpoint if err := <-errs; err != context.DeadlineExceeded { // that should not succeed t.Errorf("wanted %v, got none", context.DeadlineExceeded) } }