|
- /*
- Copyright 2017 Google LLC
-
- 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 spanner
-
- import (
- "errors"
- "fmt"
- "testing"
- "time"
-
- "github.com/golang/protobuf/proto"
- "github.com/golang/protobuf/ptypes"
- "golang.org/x/net/context"
- edpb "google.golang.org/genproto/googleapis/rpc/errdetails"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/metadata"
- "google.golang.org/grpc/status"
- )
-
- // Test if runRetryable loop deals with various errors correctly.
- func TestRetry(t *testing.T) {
- if testing.Short() {
- t.SkipNow()
- }
- responses := []error{
- status.Errorf(codes.Internal, "transport is closing"),
- status.Errorf(codes.Unknown, "unexpected EOF"),
- status.Errorf(codes.Internal, "stream terminated by RST_STREAM with error code: 2"),
- status.Errorf(codes.Unavailable, "service is currently unavailable"),
- errRetry(fmt.Errorf("just retry it")),
- }
- err := runRetryable(context.Background(), func(ct context.Context) error {
- var r error
- if len(responses) > 0 {
- r = responses[0]
- responses = responses[1:]
- }
- return r
- })
- if err != nil {
- t.Errorf("runRetryable should be able to survive all retryable errors, but it returns %v", err)
- }
- // Unretryable errors
- injErr := errors.New("this is unretryable")
- err = runRetryable(context.Background(), func(ct context.Context) error {
- return injErr
- })
- if wantErr := toSpannerError(injErr); !testEqual(err, wantErr) {
- t.Errorf("runRetryable returns error %v, want %v", err, wantErr)
- }
- // Timeout
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
- retryErr := errRetry(fmt.Errorf("still retrying"))
- err = runRetryable(ctx, func(ct context.Context) error {
- // Expect to trigger timeout in retryable runner after 10 executions.
- <-time.After(100 * time.Millisecond)
- // Let retryable runner to retry so that timeout will eventually happen.
- return retryErr
- })
- // Check error code and error message
- if wantErrCode, wantErr := codes.DeadlineExceeded, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !testEqual(err, wantErr) {
- t.Errorf("<err code, err>=\n<%v, %v>, want:\n<%v, %v>", ErrCode(err), err, wantErrCode, wantErr)
- }
- // Cancellation
- ctx, cancel = context.WithCancel(context.Background())
- retries := 3
- retryErr = errRetry(fmt.Errorf("retry before cancel"))
- err = runRetryable(ctx, func(ct context.Context) error {
- retries--
- if retries == 0 {
- cancel()
- }
- return retryErr
- })
- // Check error code, error message, retry count
- if wantErrCode, wantErr := codes.Canceled, errContextCanceled(ctx, retryErr); ErrCode(err) != wantErrCode || !testEqual(err, wantErr) || retries != 0 {
- t.Errorf("<err code, err, retries>=\n<%v, %v, %v>, want:\n<%v, %v, %v>", ErrCode(err), err, retries, wantErrCode, wantErr, 0)
- }
- }
-
- func TestRetryInfo(t *testing.T) {
- b, _ := proto.Marshal(&edpb.RetryInfo{
- RetryDelay: ptypes.DurationProto(time.Second),
- })
- trailers := map[string]string{
- retryInfoKey: string(b),
- }
- gotDelay, ok := extractRetryDelay(errRetry(toSpannerErrorWithMetadata(status.Errorf(codes.Aborted, ""), metadata.New(trailers))))
- if !ok || !testEqual(time.Second, gotDelay) {
- t.Errorf("<ok, retryDelay> = <%t, %v>, want <true, %v>", ok, gotDelay, time.Second)
- }
- }
|