Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dfawley committed Sep 18, 2019
1 parent c9a796a commit 3679c0a
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 5 deletions.
124 changes: 124 additions & 0 deletions balancer_conn_wrappers_test.go
@@ -0,0 +1,124 @@
/*
*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package grpc

import (
"testing"
"time"

"google.golang.org/grpc/balancer"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)

var _ balancer.V2Balancer = &funcBalancer{}

type funcBalancer struct {
updateClientConnState func(s balancer.ClientConnState) error
}

func (*funcBalancer) HandleSubConnStateChange(balancer.SubConn, connectivity.State) {
panic("unimplemented") // v1 API
}
func (*funcBalancer) HandleResolvedAddrs([]resolver.Address, error) {
panic("unimplemented") // v1 API
}
func (b *funcBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
return b.updateClientConnState(s)
}
func (*funcBalancer) ResolverError(error) {
panic("unimplemented") // resolver never reports error
}
func (*funcBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) {
panic("unimplemented") // we never have sub-conns
}
func (*funcBalancer) Close() {}

type funcBalancerBuilder struct {
name string
instance *funcBalancer
}

func (b *funcBalancerBuilder) Build(balancer.ClientConn, balancer.BuildOptions) balancer.Balancer {
return b.instance
}
func (b *funcBalancerBuilder) Name() string { return b.name }

// TestResolverErrorPolling injects balancer errors and verifies ResolveNow is
// called on the resolver with the appropriate backoff strategy being consulted
// between ResolveNow calls.
func (s) TestBalancerErrorResolverPolling(t *testing.T) {
defer func(o func(int) time.Duration) { resolverBackoff = o }(resolverBackoff)

boTime := time.Duration(0)
boIter := make(chan int)
resolverBackoff = func(v int) time.Duration {
t := boTime
boIter <- v
return t
}

r, rcleanup := manual.GenerateAndRegisterManualResolver()
defer rcleanup()
rn := make(chan struct{})
defer func() { close(rn) }()
r.ResolveNowCallback = func(resolver.ResolveNowOption) { rn <- struct{}{} }

uccsErr := make(chan error)
fb := &funcBalancer{
updateClientConnState: func(s balancer.ClientConnState) error {
return <-uccsErr
},
}
balancer.Register(&funcBalancerBuilder{name: "BalancerErrorResolverPolling", instance: fb})

cc, err := Dial(r.Scheme()+":///test.server", WithInsecure(), WithBalancerName("BalancerErrorResolverPolling"))
if err != nil {
t.Fatalf("Dial(_, _) = _, %v; want _, nil", err)
}
defer cc.Close()
go func() { uccsErr <- balancer.ErrBadResolverState }()
r.CC.UpdateState(resolver.State{})
timer := time.AfterFunc(5*time.Second, func() { panic("timed out polling resolver") })
// Ensure ResolveNow is called, then Backoff with the right parameter, several times
for i := 0; i < 7; i++ {
<-rn
if v := <-boIter; v != i {
t.Errorf("Backoff call %v uses value %v", i, v)
}
}
boTime = 50 * time.Millisecond
<-rn
<-boIter
go func() { uccsErr <- nil }()
r.CC.UpdateState(resolver.State{})
boTime = 0
timer.Stop()
// Wait awhile to ensure ResolveNow and Backoff are not called when the
// state is OK (i.e. polling was cancelled).
timer = time.NewTimer(60 * time.Millisecond)
select {
case <-boIter:
t.Errorf("Received Backoff call after successful resolver state")
case <-rn:
t.Errorf("Received ResolveNow after successful resolver state")
case <-timer.C:
}
}
13 changes: 10 additions & 3 deletions resolver/manual/manual.go
Expand Up @@ -30,14 +30,19 @@ import (
// NewBuilderWithScheme creates a new test resolver builder with the given scheme.
func NewBuilderWithScheme(scheme string) *Resolver {
return &Resolver{
scheme: scheme,
ResolveNowCallback: func(resolver.ResolveNowOption) {},
scheme: scheme,
}
}

// Resolver is also a resolver builder.
// It's build() function always returns itself.
type Resolver struct {
scheme string
// ResolveNowCallback is called when the ResolveNow method is called on the
// resolver. Must not be nil. Must not be changed after the resolver may
// be built.
ResolveNowCallback func(resolver.ResolveNowOption)
scheme string

// Fields actually belong to the resolver.
CC resolver.ClientConn
Expand Down Expand Up @@ -65,7 +70,9 @@ func (r *Resolver) Scheme() string {
}

// ResolveNow is a noop for Resolver.
func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {}
func (r *Resolver) ResolveNow(o resolver.ResolveNowOption) {
r.ResolveNowCallback(o)
}

// Close is a noop for Resolver.
func (*Resolver) Close() {}
Expand Down
9 changes: 7 additions & 2 deletions resolver_conn_wrapper.go
Expand Up @@ -117,7 +117,7 @@ func (ccr *ccResolverWrapper) close() {
ccr.mu.Unlock()
}

var resolverBackoff = backoff.Exponential{MaxDelay: 2 * time.Minute}
var resolverBackoff = backoff.Exponential{MaxDelay: 2 * time.Minute}.Backoff

// poll begins or ends asynchronous polling of the resolver based on whether
// err is ErrBadResolverState.
Expand All @@ -141,7 +141,7 @@ func (ccr *ccResolverWrapper) poll(err error) {
go func() {
for i := 0; ; i++ {
ccr.resolveNow(resolver.ResolveNowOption{})
t := time.NewTimer(resolverBackoff.Backoff(i))
t := time.NewTimer(resolverBackoff(i))
select {
case <-p:
t.Stop()
Expand All @@ -151,6 +151,11 @@ func (ccr *ccResolverWrapper) poll(err error) {
t.Stop()
return
case <-t.C:
select {
case <-p:
return
default:
}
// Timer expired; re-resolve.
}
}
Expand Down
54 changes: 54 additions & 0 deletions resolver_conn_wrapper_test.go
Expand Up @@ -19,12 +19,14 @@
package grpc

import (
"errors"
"fmt"
"net"
"testing"
"time"

"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)

func (s) TestParseTarget(t *testing.T) {
Expand Down Expand Up @@ -114,3 +116,55 @@ func (s) TestDialParseTargetUnknownScheme(t *testing.T) {
}
}
}

// TestResolverErrorPolling injects resolver errors and verifies ResolveNow is
// called with the appropriate backoff strategy being consulted between
// ResolveNow calls.
func (s) TestResolverErrorPolling(t *testing.T) {
defer func(o func(int) time.Duration) { resolverBackoff = o }(resolverBackoff)

boTime := time.Duration(0)
boIter := make(chan int)
resolverBackoff = func(v int) time.Duration {
t := boTime
boIter <- v
return t
}

r, rcleanup := manual.GenerateAndRegisterManualResolver()
defer rcleanup()
rn := make(chan struct{})
defer func() { close(rn) }()
r.ResolveNowCallback = func(resolver.ResolveNowOption) { rn <- struct{}{} }

cc, err := Dial(r.Scheme()+":///test.server", WithInsecure())
if err != nil {
t.Fatalf("Dial(_, _) = _, %v; want _, nil", err)
}
defer cc.Close()
r.CC.ReportError(errors.New("res err"))
timer := time.AfterFunc(5*time.Second, func() { panic("timed out polling resolver") })
// Ensure ResolveNow is called, then Backoff with the right parameter, several times
for i := 0; i < 7; i++ {
<-rn
if v := <-boIter; v != i {
t.Errorf("Backoff call %v uses value %v", i, v)
}
}
boTime = 50 * time.Millisecond
<-rn
<-boIter
r.CC.UpdateState(resolver.State{})
boTime = 0
timer.Stop()
// Wait awhile to ensure ResolveNow and Backoff are not called when the
// state is OK (i.e. polling was cancelled).
timer = time.NewTimer(60 * time.Millisecond)
select {
case <-boIter:
t.Errorf("Received Backoff call after successful resolver state")
case <-rn:
t.Errorf("Received ResolveNow after successful resolver state")
case <-timer.C:
}
}

0 comments on commit 3679c0a

Please sign in to comment.