1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package otlptracegrpc
16
17 import (
18 "context"
19 "testing"
20 "time"
21
22 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/require"
24 "google.golang.org/genproto/googleapis/rpc/errdetails"
25 "google.golang.org/grpc/codes"
26 "google.golang.org/grpc/status"
27 "google.golang.org/protobuf/types/known/durationpb"
28 )
29
30 func TestThrottleDelay(t *testing.T) {
31 c := codes.ResourceExhausted
32 testcases := []struct {
33 status *status.Status
34 wantOK bool
35 wantDuration time.Duration
36 }{
37 {
38 status: status.New(c, "NoRetryInfo"),
39 wantOK: false,
40 wantDuration: 0,
41 },
42 {
43 status: func() *status.Status {
44 s, err := status.New(c, "SingleRetryInfo").WithDetails(
45 &errdetails.RetryInfo{
46 RetryDelay: durationpb.New(15 * time.Millisecond),
47 },
48 )
49 require.NoError(t, err)
50 return s
51 }(),
52 wantOK: true,
53 wantDuration: 15 * time.Millisecond,
54 },
55 {
56 status: func() *status.Status {
57 s, err := status.New(c, "ErrorInfo").WithDetails(
58 &errdetails.ErrorInfo{Reason: "no throttle detail"},
59 )
60 require.NoError(t, err)
61 return s
62 }(),
63 wantOK: false,
64 wantDuration: 0,
65 },
66 {
67 status: func() *status.Status {
68 s, err := status.New(c, "ErrorAndRetryInfo").WithDetails(
69 &errdetails.ErrorInfo{Reason: "with throttle detail"},
70 &errdetails.RetryInfo{
71 RetryDelay: durationpb.New(13 * time.Minute),
72 },
73 )
74 require.NoError(t, err)
75 return s
76 }(),
77 wantOK: true,
78 wantDuration: 13 * time.Minute,
79 },
80 {
81 status: func() *status.Status {
82 s, err := status.New(c, "DoubleRetryInfo").WithDetails(
83 &errdetails.RetryInfo{
84 RetryDelay: durationpb.New(13 * time.Minute),
85 },
86 &errdetails.RetryInfo{
87 RetryDelay: durationpb.New(15 * time.Minute),
88 },
89 )
90 require.NoError(t, err)
91 return s
92 }(),
93 wantOK: true,
94 wantDuration: 13 * time.Minute,
95 },
96 }
97
98 for _, tc := range testcases {
99 t.Run(tc.status.Message(), func(t *testing.T) {
100 ok, d := throttleDelay(tc.status)
101 assert.Equal(t, tc.wantOK, ok)
102 assert.Equal(t, tc.wantDuration, d)
103 })
104 }
105 }
106
107 func TestRetryable(t *testing.T) {
108 retryableCodes := map[codes.Code]bool{
109 codes.OK: false,
110 codes.Canceled: true,
111 codes.Unknown: false,
112 codes.InvalidArgument: false,
113 codes.DeadlineExceeded: true,
114 codes.NotFound: false,
115 codes.AlreadyExists: false,
116 codes.PermissionDenied: false,
117 codes.ResourceExhausted: false,
118 codes.FailedPrecondition: false,
119 codes.Aborted: true,
120 codes.OutOfRange: true,
121 codes.Unimplemented: false,
122 codes.Internal: false,
123 codes.Unavailable: true,
124 codes.DataLoss: true,
125 codes.Unauthenticated: false,
126 }
127
128 for c, want := range retryableCodes {
129 got, _ := retryable(status.Error(c, ""))
130 assert.Equalf(t, want, got, "evaluate(%s)", c)
131 }
132 }
133
134 func TestRetryableGRPCStatusResourceExhaustedWithRetryInfo(t *testing.T) {
135 delay := 15 * time.Millisecond
136 s, err := status.New(codes.ResourceExhausted, "WithRetryInfo").WithDetails(
137 &errdetails.RetryInfo{
138 RetryDelay: durationpb.New(delay),
139 },
140 )
141 require.NoError(t, err)
142
143 ok, d := retryableGRPCStatus(s)
144 assert.True(t, ok)
145 assert.Equal(t, delay, d)
146 }
147
148 func TestUnstartedStop(t *testing.T) {
149 client := NewClient()
150 assert.ErrorIs(t, client.Stop(context.Background()), errAlreadyStopped)
151 }
152
153 func TestUnstartedUploadTrace(t *testing.T) {
154 client := NewClient()
155 assert.ErrorIs(t, client.UploadTraces(context.Background(), nil), errShutdown)
156 }
157
158 func TestExportContextHonorsParentDeadline(t *testing.T) {
159 now := time.Now()
160 ctx, cancel := context.WithDeadline(context.Background(), now)
161 t.Cleanup(cancel)
162
163
164 client := newClient(WithTimeout(0))
165 eCtx, eCancel := client.exportContext(ctx)
166 t.Cleanup(eCancel)
167
168 deadline, ok := eCtx.Deadline()
169 assert.True(t, ok, "deadline not propagated to child context")
170 assert.Equal(t, now, deadline)
171 }
172
173 func TestExportContextHonorsClientTimeout(t *testing.T) {
174
175 client := newClient(WithTimeout(1 * time.Second))
176 ctx, cancel := client.exportContext(context.Background())
177 t.Cleanup(cancel)
178
179 _, ok := ctx.Deadline()
180 assert.True(t, ok, "timeout not set as deadline for child context")
181 }
182
183 func TestExportContextLinksStopSignal(t *testing.T) {
184 rootCtx := context.Background()
185
186 client := newClient(WithInsecure())
187 t.Cleanup(func() { require.NoError(t, client.Stop(rootCtx)) })
188 require.NoError(t, client.Start(rootCtx))
189
190 ctx, cancel := client.exportContext(rootCtx)
191 t.Cleanup(cancel)
192
193 require.False(t, func() bool {
194 select {
195 case <-ctx.Done():
196 return true
197 default:
198 }
199 return false
200 }(), "context should not be done prior to canceling it")
201
202
203
204 client.stopFunc()
205
206
207 assert.Eventually(t, func() bool {
208 select {
209 case <-ctx.Done():
210 return true
211 default:
212 }
213 return false
214 }, 10*time.Second, time.Microsecond)
215 }
216
View as plain text