Skip to content

Commit fe8934d

Browse files
dmitryaxsbylica-splunk
authored andcommitted
[chore] [exporter/signalfx] Rework property/tags update tests (open-telemetry#36086)
Rework the tests to explicitly control requests handling by the mock server. Use channels to control start and finish of request handling instead of relying on timer and timeouts. All the tests in the package are now executed in 1.5 seconds compared to 19 seconds as before the change. This change also unblocks open-telemetry#36044 which can be covered after this
1 parent 81d0f4a commit fe8934d

File tree

1 file changed

+123
-94
lines changed

1 file changed

+123
-94
lines changed

exporter/signalfxexporter/internal/dimensions/dimclient_test.go

Lines changed: 123 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
package dimensions
55

66
import (
7-
"context"
87
"encoding/json"
9-
"log"
108
"net/http"
119
"net/http/httptest"
1210
"net/url"
@@ -30,97 +28,106 @@ type dim struct {
3028
TagsToRemove []string `json:"tagsToRemove"`
3129
}
3230

33-
func waitForDims(dimCh <-chan dim, count, waitSeconds int) []dim { // nolint: unparam
34-
var dims []dim
35-
timeout := time.After(time.Duration(waitSeconds) * time.Second)
36-
37-
loop:
38-
for {
39-
select {
40-
case d := <-dimCh:
41-
dims = append(dims, d)
42-
if len(dims) >= count {
43-
break loop
44-
}
45-
case <-timeout:
46-
break loop
47-
}
48-
}
49-
50-
return dims
31+
type testServer struct {
32+
startCh chan struct{}
33+
finishCh chan struct{}
34+
acceptedDims []dim
35+
server *httptest.Server
36+
respCode int
37+
requestCount *atomic.Int32
5138
}
5239

53-
func makeHandler(dimCh chan<- dim, forcedResp *atomic.Int32) http.HandlerFunc {
54-
forcedResp.Store(200)
40+
func (ts *testServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
41+
ts.requestCount.Add(1)
42+
<-ts.startCh
5543

56-
return func(rw http.ResponseWriter, r *http.Request) {
57-
forcedRespInt := int(forcedResp.Load())
58-
if forcedRespInt != 200 {
59-
rw.WriteHeader(forcedRespInt)
60-
return
61-
}
44+
if ts.respCode != http.StatusOK {
45+
rw.WriteHeader(ts.respCode)
46+
ts.finishCh <- struct{}{}
47+
return
48+
}
6249

63-
log.Printf("Test server got request: %s", r.URL.Path)
50+
match := patchPathRegexp.FindStringSubmatch(r.URL.Path)
51+
if match == nil {
52+
rw.WriteHeader(http.StatusNotFound)
53+
ts.finishCh <- struct{}{}
54+
return
55+
}
6456

65-
if r.Method != "PATCH" {
66-
rw.WriteHeader(http.StatusNotFound)
67-
return
68-
}
57+
var bodyDim dim
58+
if err := json.NewDecoder(r.Body).Decode(&bodyDim); err != nil {
59+
rw.WriteHeader(http.StatusBadRequest)
60+
ts.finishCh <- struct{}{}
61+
return
62+
}
63+
bodyDim.Key = match[1]
64+
bodyDim.Value = match[2]
6965

70-
match := patchPathRegexp.FindStringSubmatch(r.URL.Path)
71-
if match == nil {
72-
rw.WriteHeader(http.StatusNotFound)
73-
return
74-
}
66+
ts.acceptedDims = append(ts.acceptedDims, bodyDim)
7567

76-
var bodyDim dim
77-
if err := json.NewDecoder(r.Body).Decode(&bodyDim); err != nil {
78-
rw.WriteHeader(400)
79-
return
80-
}
81-
bodyDim.Key = match[1]
82-
bodyDim.Value = match[2]
68+
ts.finishCh <- struct{}{}
69+
rw.WriteHeader(http.StatusOK)
70+
}
8371

84-
dimCh <- bodyDim
72+
// startHandling unblocks the server to handle the request and waits until the request is processed.
73+
func (ts *testServer) handleRequest() {
74+
ts.startCh <- struct{}{}
75+
<-ts.finishCh
76+
}
8577

86-
rw.WriteHeader(http.StatusOK)
78+
func (ts *testServer) shutdown() {
79+
ts.reset()
80+
if ts.server != nil {
81+
ts.server.Close()
8782
}
8883
}
8984

90-
func setup(t *testing.T) (*DimensionClient, chan dim, *atomic.Int32, context.CancelFunc) {
91-
dimCh := make(chan dim)
85+
func (ts *testServer) reset() {
86+
if ts.startCh != nil {
87+
close(ts.startCh)
88+
ts.startCh = make(chan struct{})
89+
}
90+
if ts.finishCh != nil {
91+
close(ts.finishCh)
92+
ts.finishCh = make(chan struct{})
93+
}
94+
ts.acceptedDims = nil
95+
ts.respCode = http.StatusOK
96+
ts.requestCount.Store(0)
97+
}
9298

93-
forcedResp := &atomic.Int32{}
94-
server := httptest.NewServer(makeHandler(dimCh, forcedResp))
99+
func setupTestClientServer(t *testing.T) (*DimensionClient, *testServer) {
100+
ts := &testServer{
101+
startCh: make(chan struct{}),
102+
finishCh: make(chan struct{}),
103+
respCode: http.StatusOK,
104+
requestCount: new(atomic.Int32),
105+
}
106+
ts.server = httptest.NewServer(ts)
95107

96-
serverURL, err := url.Parse(server.URL)
108+
serverURL, err := url.Parse(ts.server.URL)
97109
require.NoError(t, err, "failed to get server URL", err)
98110

99-
ctx, cancel := context.WithCancel(context.Background())
100-
go func() {
101-
<-ctx.Done()
102-
server.Close()
103-
}()
104-
105111
client := NewDimensionClient(
106112
DimensionClientOptions{
107113
APIURL: serverURL,
108114
LogUpdates: true,
109115
Logger: zap.NewNop(),
110-
SendDelay: time.Second,
116+
SendDelay: 100 * time.Millisecond,
111117
MaxBuffered: 10,
112118
})
113119
client.Start()
114120

115-
return client, dimCh, forcedResp, cancel
121+
return client, ts
116122
}
117123

118124
func TestDimensionClient(t *testing.T) {
119-
client, dimCh, forcedResp, cancel := setup(t)
120-
defer cancel()
125+
client, server := setupTestClientServer(t)
126+
defer server.shutdown()
121127
defer client.Shutdown()
122128

123129
t.Run("send dimension update with properties and tags", func(t *testing.T) {
130+
server.reset()
124131
require.NoError(t, client.acceptDimension(&DimensionUpdate{
125132
Name: "host",
126133
Value: "test-box",
@@ -135,7 +142,7 @@ func TestDimensionClient(t *testing.T) {
135142
},
136143
}))
137144

138-
dims := waitForDims(dimCh, 1, 3)
145+
server.handleRequest()
139146
require.Equal(t, []dim{
140147
{
141148
Key: "host",
@@ -148,10 +155,12 @@ func TestDimensionClient(t *testing.T) {
148155
Tags: []string{"active"},
149156
TagsToRemove: []string{"terminated"},
150157
},
151-
}, dims)
158+
}, server.acceptedDims)
159+
require.EqualValues(t, 1, server.requestCount.Load())
152160
})
153161

154162
t.Run("same dimension with different values", func(t *testing.T) {
163+
server.reset()
155164
require.NoError(t, client.acceptDimension(&DimensionUpdate{
156165
Name: "host",
157166
Value: "test-box",
@@ -163,7 +172,7 @@ func TestDimensionClient(t *testing.T) {
163172
},
164173
}))
165174

166-
dims := waitForDims(dimCh, 1, 3)
175+
server.handleRequest()
167176
require.Equal(t, []dim{
168177
{
169178
Key: "host",
@@ -173,11 +182,13 @@ func TestDimensionClient(t *testing.T) {
173182
},
174183
TagsToRemove: []string{"active"},
175184
},
176-
}, dims)
185+
}, server.acceptedDims)
186+
require.EqualValues(t, 1, server.requestCount.Load())
177187
})
178188

179189
t.Run("send a distinct prop/tag set for existing dim with server error", func(t *testing.T) {
180-
forcedResp.Store(500)
190+
server.reset()
191+
server.respCode = http.StatusInternalServerError
181192

182193
// send a distinct prop/tag set for same dim with an error
183194
require.NoError(t, client.acceptDimension(&DimensionUpdate{
@@ -190,11 +201,11 @@ func TestDimensionClient(t *testing.T) {
190201
"running": true,
191202
},
192203
}))
193-
dims := waitForDims(dimCh, 1, 3)
194-
require.Empty(t, dims)
204+
server.handleRequest()
205+
require.Empty(t, server.acceptedDims)
195206

196-
forcedResp.Store(200)
197-
dims = waitForDims(dimCh, 1, 3)
207+
server.respCode = http.StatusOK
208+
server.handleRequest()
198209

199210
// After the server recovers the dim should be resent.
200211
require.Equal(t, []dim{
@@ -206,11 +217,13 @@ func TestDimensionClient(t *testing.T) {
206217
},
207218
Tags: []string{"running"},
208219
},
209-
}, dims)
220+
}, server.acceptedDims)
221+
require.EqualValues(t, 2, server.requestCount.Load())
210222
})
211223

212224
t.Run("does not retry 4xx responses", func(t *testing.T) {
213-
forcedResp.Store(400)
225+
server.reset()
226+
server.respCode = http.StatusBadRequest
214227

215228
// send a distinct prop/tag set for same dim with an error
216229
require.NoError(t, client.acceptDimension(&DimensionUpdate{
@@ -220,16 +233,19 @@ func TestDimensionClient(t *testing.T) {
220233
"z": newString("y"),
221234
},
222235
}))
223-
dims := waitForDims(dimCh, 1, 3)
224-
require.Empty(t, dims)
236+
server.handleRequest()
237+
238+
require.Empty(t, server.acceptedDims)
225239

226-
forcedResp.Store(200)
227-
dims = waitForDims(dimCh, 1, 3)
228-
require.Empty(t, dims)
240+
server.respCode = http.StatusOK
241+
242+
// there should be no retries
243+
require.EqualValues(t, 1, server.requestCount.Load())
229244
})
230245

231246
t.Run("does retry 404 responses", func(t *testing.T) {
232-
forcedResp.Store(404)
247+
server.reset()
248+
server.respCode = http.StatusNotFound
233249

234250
// send a distinct prop/tag set for same dim with an error
235251
require.NoError(t, client.acceptDimension(&DimensionUpdate{
@@ -240,11 +256,11 @@ func TestDimensionClient(t *testing.T) {
240256
},
241257
}))
242258

243-
dims := waitForDims(dimCh, 1, 3)
244-
require.Empty(t, dims)
259+
server.handleRequest()
260+
require.Empty(t, server.acceptedDims)
245261

246-
forcedResp.Store(200)
247-
dims = waitForDims(dimCh, 1, 3)
262+
server.respCode = http.StatusOK
263+
server.handleRequest()
248264
require.Equal(t, []dim{
249265
{
250266
Key: "AWSUniqueID",
@@ -253,10 +269,13 @@ func TestDimensionClient(t *testing.T) {
253269
"z": newString("x"),
254270
},
255271
},
256-
}, dims)
272+
}, server.acceptedDims)
273+
require.EqualValues(t, 2, server.requestCount.Load())
257274
})
258275

259276
t.Run("send successive quick updates to same dim", func(t *testing.T) {
277+
server.reset()
278+
260279
require.NoError(t, client.acceptDimension(&DimensionUpdate{
261280
Name: "AWSUniqueID",
262281
Value: "abcd",
@@ -292,7 +311,7 @@ func TestDimensionClient(t *testing.T) {
292311
},
293312
}))
294313

295-
dims := waitForDims(dimCh, 1, 3)
314+
server.handleRequest()
296315

297316
require.Equal(t, []dim{
298317
{
@@ -305,13 +324,14 @@ func TestDimensionClient(t *testing.T) {
305324
Tags: []string{"dev"},
306325
TagsToRemove: []string{"running"},
307326
},
308-
}, dims)
327+
}, server.acceptedDims)
328+
require.EqualValues(t, 1, server.requestCount.Load())
309329
})
310330
}
311331

312332
func TestFlappyUpdates(t *testing.T) {
313-
client, dimCh, _, cancel := setup(t)
314-
defer cancel()
333+
client, server := setupTestClientServer(t)
334+
defer server.shutdown()
315335
defer client.Shutdown()
316336

317337
// Do some flappy updates
@@ -333,7 +353,10 @@ func TestFlappyUpdates(t *testing.T) {
333353
}))
334354
}
335355

336-
dims := waitForDims(dimCh, 2, 3)
356+
// handle 2 requests
357+
server.handleRequest()
358+
server.handleRequest()
359+
337360
require.ElementsMatch(t, []dim{
338361
{
339362
Key: "pod_uid",
@@ -345,12 +368,15 @@ func TestFlappyUpdates(t *testing.T) {
345368
Value: "efgh",
346369
Properties: map[string]*string{"index": newString("4")},
347370
},
348-
}, dims)
371+
}, server.acceptedDims)
372+
require.EqualValues(t, 2, server.requestCount.Load())
349373
}
350374

375+
// TODO: Update the dimension update client to never send empty dimension key or value
351376
func TestInvalidUpdatesNotSent(t *testing.T) {
352-
client, dimCh, _, cancel := setup(t)
353-
defer cancel()
377+
t.Skip("This test causes data race because empty dimension key or value result in 404s which causes infinite retries")
378+
client, server := setupTestClientServer(t)
379+
defer server.shutdown()
354380
defer client.Shutdown()
355381
require.NoError(t, client.acceptDimension(&DimensionUpdate{
356382
Name: "host",
@@ -363,6 +389,8 @@ func TestInvalidUpdatesNotSent(t *testing.T) {
363389
"active": true,
364390
},
365391
}))
392+
server.handleRequest()
393+
366394
require.NoError(t, client.acceptDimension(&DimensionUpdate{
367395
Name: "",
368396
Value: "asdf",
@@ -374,9 +402,10 @@ func TestInvalidUpdatesNotSent(t *testing.T) {
374402
"active": true,
375403
},
376404
}))
405+
server.handleRequest()
377406

378-
dims := waitForDims(dimCh, 2, 3)
379-
require.Empty(t, dims)
407+
require.EqualValues(t, 2, server.requestCount.Load())
408+
require.Empty(t, server.acceptedDims)
380409
}
381410

382411
func newString(s string) *string {

0 commit comments

Comments
 (0)