// Copyright 2016 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 lro supports Long Running Operations for the Google Cloud Libraries. // // This package is still experimental and subject to change. package longrunning import ( "context" "errors" "testing" "time" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/duration" gax "github.com/googleapis/gax-go/v2" pb "google.golang.org/genproto/googleapis/longrunning" status "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) type getterService struct { operationsClient // clock represents the fake current time of the service. // It is the running sum of the of the duration we have slept. clock time.Duration // getTimes records the times at which GetOperation is called. getTimes []time.Duration // results are the fake results that GetOperation should return. results []*pb.Operation } func (s *getterService) GetOperation(context.Context, *pb.GetOperationRequest, ...gax.CallOption) (*pb.Operation, error) { i := len(s.getTimes) s.getTimes = append(s.getTimes, s.clock) if i >= len(s.results) { return nil, errors.New("unexpected call") } return s.results[i], nil } func (s *getterService) sleeper() sleeper { return func(_ context.Context, d time.Duration) error { s.clock += d return nil } } func TestWait(t *testing.T) { responseDur := ptypes.DurationProto(42 * time.Second) responseAny, err := ptypes.MarshalAny(responseDur) if err != nil { t.Fatal(err) } s := &getterService{ results: []*pb.Operation{ {Name: "foo"}, {Name: "foo"}, {Name: "foo"}, {Name: "foo"}, {Name: "foo"}, { Name: "foo", Done: true, Result: &pb.Operation_Response{ Response: responseAny, }, }, }, } op := &Operation{ c: s, proto: &pb.Operation{Name: "foo"}, } if op.Done() { t.Fatal("operation should not have completed yet") } var resp duration.Duration bo := gax.Backoff{ Initial: 1 * time.Second, Max: 3 * time.Second, } if err := op.wait(context.Background(), &resp, &bo, s.sleeper()); err != nil { t.Fatal(err) } if !proto.Equal(&resp, responseDur) { t.Errorf("response, got %v, want %v", resp, responseDur) } if !op.Done() { t.Errorf("operation should have completed") } maxWait := []time.Duration{ 1 * time.Second, 2 * time.Second, 3 * time.Second, 3 * time.Second, 3 * time.Second, } for i := 0; i < len(s.getTimes)-1; i++ { w := s.getTimes[i+1] - s.getTimes[i] if mw := maxWait[i]; w > mw { t.Errorf("backoff, waited %s, max %s", w, mw) } } } func TestPollRequestError(t *testing.T) { const opName = "foo" // All calls error. s := &getterService{} op := &Operation{ c: s, proto: &pb.Operation{Name: opName}, } if err := op.Poll(context.Background(), nil); err == nil { t.Fatalf("Poll should error") } if n := op.Name(); n != opName { t.Errorf("operation name, got %q, want %q", n, opName) } if op.Done() { t.Errorf("operation should not have completed; we failed to fetch state") } } func TestPollErrorResult(t *testing.T) { const ( errCode = codes.NotFound errMsg = "my error" ) op := &Operation{ proto: &pb.Operation{ Name: "foo", Done: true, Result: &pb.Operation_Error{ Error: &status.Status{ Code: int32(errCode), Message: errMsg, }, }, }, } err := op.Poll(context.Background(), nil) if got := grpc.Code(err); got != errCode { t.Errorf("error code, want %s, got %s", errCode, got) } if got := grpc.ErrorDesc(err); got != errMsg { t.Errorf("error code, want %s, got %s", errMsg, got) } if !op.Done() { t.Errorf("operation should have completed") } } type errService struct { operationsClient errCancel, errDelete error } func (s *errService) CancelOperation(context.Context, *pb.CancelOperationRequest, ...gax.CallOption) error { return s.errCancel } func (s *errService) DeleteOperation(context.Context, *pb.DeleteOperationRequest, ...gax.CallOption) error { return s.errDelete } func TestCancelReturnsError(t *testing.T) { s := &errService{ errCancel: errors.New("cancel error"), } op := &Operation{ c: s, proto: &pb.Operation{Name: "foo"}, } if got, want := op.Cancel(context.Background()), s.errCancel; got != want { t.Errorf("cancel, got error %s, want %s", got, want) } } func TestDeleteReturnsError(t *testing.T) { s := &errService{ errDelete: errors.New("delete error"), } op := &Operation{ c: s, proto: &pb.Operation{Name: "foo"}, } if got, want := op.Delete(context.Background()), s.errDelete; got != want { t.Errorf("cancel, got error %s, want %s", got, want) } }