1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package longrunning
19
20 import (
21 "context"
22 "errors"
23 "testing"
24 "time"
25
26 pb "cloud.google.com/go/longrunning/autogen/longrunningpb"
27 gax "github.com/googleapis/gax-go/v2"
28 "github.com/googleapis/gax-go/v2/apierror"
29 "google.golang.org/genproto/googleapis/rpc/errdetails"
30 rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
31 "google.golang.org/grpc"
32 "google.golang.org/grpc/codes"
33 "google.golang.org/grpc/status"
34 "google.golang.org/protobuf/proto"
35 "google.golang.org/protobuf/types/known/anypb"
36 "google.golang.org/protobuf/types/known/durationpb"
37 )
38
39 type getterService struct {
40 operationsClient
41
42
43
44 clock time.Duration
45
46
47 getTimes []time.Duration
48
49
50 results []*pb.Operation
51 }
52
53 func (s *getterService) GetOperation(context.Context, *pb.GetOperationRequest, ...gax.CallOption) (*pb.Operation, error) {
54 i := len(s.getTimes)
55 s.getTimes = append(s.getTimes, s.clock)
56 if i >= len(s.results) {
57 return nil, errors.New("unexpected call")
58 }
59 return s.results[i], nil
60 }
61
62 func (s *getterService) sleeper() sleeper {
63 return func(_ context.Context, d time.Duration) error {
64 s.clock += d
65 return nil
66 }
67 }
68
69 func TestWait(t *testing.T) {
70 responseDur := durationpb.New(42 * time.Second)
71 responseAny, err := anypb.New(responseDur)
72 if err != nil {
73 t.Fatal(err)
74 }
75
76 s := &getterService{
77 results: []*pb.Operation{
78 {Name: "foo"},
79 {Name: "foo"},
80 {Name: "foo"},
81 {Name: "foo"},
82 {Name: "foo"},
83 {
84 Name: "foo",
85 Done: true,
86 Result: &pb.Operation_Response{
87 Response: responseAny,
88 },
89 },
90 },
91 }
92 op := &Operation{
93 c: s,
94 proto: &pb.Operation{Name: "foo"},
95 }
96 if op.Done() {
97 t.Fatal("operation should not have completed yet")
98 }
99
100 var resp durationpb.Duration
101 bo := gax.Backoff{
102 Initial: 1 * time.Second,
103 Max: 3 * time.Second,
104 }
105 if err := op.wait(context.Background(), &resp, &bo, s.sleeper()); err != nil {
106 t.Fatal(err)
107 }
108 if !proto.Equal(&resp, responseDur) {
109 t.Errorf("response, got %v, want %v", resp, responseDur)
110 }
111 if !op.Done() {
112 t.Errorf("operation should have completed")
113 }
114
115 maxWait := []time.Duration{
116 1 * time.Second,
117 2 * time.Second,
118 3 * time.Second,
119 3 * time.Second,
120 3 * time.Second,
121 }
122 for i := 0; i < len(s.getTimes)-1; i++ {
123 w := s.getTimes[i+1] - s.getTimes[i]
124 if mw := maxWait[i]; w > mw {
125 t.Errorf("backoff, waited %s, max %s", w, mw)
126 }
127 }
128 }
129
130 func TestPollRequestError(t *testing.T) {
131 const opName = "foo"
132
133
134 s := &getterService{}
135 op := &Operation{
136 c: s,
137 proto: &pb.Operation{Name: opName},
138 }
139 if err := op.Poll(context.Background(), nil); err == nil {
140 t.Fatalf("Poll should error")
141 }
142 if n := op.Name(); n != opName {
143 t.Errorf("operation name, got %q, want %q", n, opName)
144 }
145 if op.Done() {
146 t.Errorf("operation should not have completed; we failed to fetch state")
147 }
148 }
149
150 func TestPollErrorResult(t *testing.T) {
151 const (
152 errCode = codes.NotFound
153 errMsg = "my error"
154 )
155 details := &errdetails.ErrorInfo{Reason: "things happen"}
156 a, err := anypb.New(details)
157 if err != nil {
158 t.Fatalf("anypb.New() = %v", err)
159 }
160 op := &Operation{
161 proto: &pb.Operation{
162 Name: "foo",
163 Done: true,
164 Result: &pb.Operation_Error{
165 Error: &rpcstatus.Status{
166 Code: int32(errCode),
167 Message: errMsg,
168 Details: []*anypb.Any{a},
169 },
170 },
171 },
172 }
173 err = op.Poll(context.Background(), nil)
174 if got := status.Code(err); got != errCode {
175 t.Errorf("error code, want %s, got %s", errCode, got)
176 }
177 if got := grpc.ErrorDesc(err); got != errMsg {
178 t.Errorf("error code, want %s, got %s", errMsg, got)
179 }
180 if !op.Done() {
181 t.Errorf("operation should have completed")
182 }
183 var ae *apierror.APIError
184 errors.As(err, &ae)
185 if got := ae.Details().ErrorInfo.Reason; got != details.Reason {
186 t.Errorf("got %q, want %q", got, details.Reason)
187 }
188 }
189
190 type errService struct {
191 operationsClient
192 errCancel, errDelete error
193 }
194
195 func (s *errService) CancelOperation(context.Context, *pb.CancelOperationRequest, ...gax.CallOption) error {
196 return s.errCancel
197 }
198
199 func (s *errService) DeleteOperation(context.Context, *pb.DeleteOperationRequest, ...gax.CallOption) error {
200 return s.errDelete
201 }
202
203 func TestCancelReturnsError(t *testing.T) {
204 s := &errService{
205 errCancel: errors.New("cancel error"),
206 }
207 op := &Operation{
208 c: s,
209 proto: &pb.Operation{Name: "foo"},
210 }
211 if got, want := op.Cancel(context.Background()), s.errCancel; got != want {
212 t.Errorf("cancel, got error %s, want %s", got, want)
213 }
214 }
215
216 func TestDeleteReturnsError(t *testing.T) {
217 s := &errService{
218 errDelete: errors.New("delete error"),
219 }
220 op := &Operation{
221 c: s,
222 proto: &pb.Operation{Name: "foo"},
223 }
224 if got, want := op.Delete(context.Background()), s.errDelete; got != want {
225 t.Errorf("cancel, got error %s, want %s", got, want)
226 }
227 }
228
View as plain text