Skip to content

Commit b44f32e

Browse files
authored
fix: test race condition (#25)
* fix: test race condition * refactor(protocol/dataunit): rename test variables for clarity * test(protocol/dataunit): log data sent by client * test(protocol/dataunit): update error message with correct method name * test(protocol/dataunit): log data sent by server * test(protocol/dataunit): remove test log output * doc(protocol/dataunit): explain echo client test * test(protocol/dataunit): correct the arg order for echoServer * refactor(protocol/dataunit): use math/rand/v2 * test(protocol/dataunit): fix data race using channels * doc(protocol/dataunit): explain echo client test * refactor(protocol/dataunit): explicitly ignore return values * refactor(protocol/dataunit): use errors.Is * Revert "refactor(protocol/dataunit): use errors.Is" This reverts commit 8b59d50. * typo: use canceled not cancelled * refactor(protocol/dataunit): replace rand.Int64N with rand.N
1 parent d0a32c5 commit b44f32e

File tree

1 file changed

+86
-37
lines changed

1 file changed

+86
-37
lines changed

protocol/dataunit/client_server_test.go

+86-37
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,74 @@ import (
44
"bytes"
55
"context"
66
"errors"
7-
"math/rand"
7+
"fmt"
8+
"math/rand/v2"
89
"net"
910
"strconv"
1011
"sync"
1112
"testing"
1213
"time"
1314
)
1415

16+
// TestEchoClientAndServer validates the EPP server is able to read the data
17+
// provided in the client request and to respond back correctly.
18+
//
19+
// It does this by passing an instance of a `Server` to `echoServer()`, which
20+
// runs in a goroutine. The echo server runs forever or until the passed in
21+
// context is canceled. Inside the echo server it checks for any context
22+
// cancellations and returns early if so (it will optionally record the error if
23+
// the error is not an expected type). Otherwise, it handles 10 requests at a
24+
// time, spinning up each request in a new goroutine. Each goroutine handles a
25+
// response to the client request. Each request is sending a unique 'data unit'
26+
// to be processed and the response is sent back to the `serverConn`.
27+
//
28+
// We use a `net.Pipe()` to simulate the client/server request/response flows.
29+
// i.e. a write to `clientConn` is readable via `serverConn` (and vice-versa).
30+
//
31+
// So c.ExchangeDataUnit() writes data to the `clientConn` which causes a copy
32+
// of the data to be written into `serverConn` for it to read. Then writing to
33+
// `serverConn` causes a copy of that data to be written into `clientConn` for
34+
// it to read. Simulating a bidirectional network connection.
1535
func TestEchoClientAndServer(t *testing.T) {
1636
ctx, cancel := context.WithCancelCause(context.Background())
1737
defer cancel(errTestDone)
1838

1939
clientConn, serverConn := net.Pipe()
2040
c := &Client{Conn: clientConn}
2141
s := &Server{Conn: serverConn}
22-
go echoServer(t, ctx, s)
42+
n := 100
43+
echoServerErr := make(chan error, n)
44+
45+
go echoServer(ctx, s, echoServerErr)
2346

2447
sem := make(chan struct{}, 2)
2548
var wg sync.WaitGroup
2649

27-
for i := 0; i < 100; i++ {
28-
if t.Failed() {
29-
break
30-
}
31-
req := []byte(strconv.FormatInt(int64(i), 10))
50+
for i := 0; i < n; i++ {
51+
data := []byte(strconv.FormatInt(int64(i), 10))
3252
sem <- struct{}{}
3353
wg.Add(1)
3454
go func() {
3555
defer wg.Done()
3656
ctx, cancel := context.WithCancel(ctx)
3757
defer cancel()
3858
time.Sleep(randDuration(10 * time.Millisecond))
39-
res, err := c.ExchangeDataUnit(ctx, req)
59+
res, err := c.ExchangeDataUnit(ctx, data)
60+
// t.Logf("data received from server connection: %s\n", res)
4061
if err != nil {
41-
t.Errorf("ExchangeDataUnit(): err == %v", err)
62+
echoServerErr <- fmt.Errorf("ExchangeDataUnit(): err == %w", err)
4263
}
43-
if !bytes.Equal(req, res) {
44-
t.Errorf("ExchangeDataUnit(): got %s, expected %s", string(res), string(req))
64+
if !bytes.Equal(data, res) {
65+
echoServerErr <- fmt.Errorf("ExchangeDataUnit(): got %s, expected %s", string(res), string(data))
4566
}
4667
<-sem
4768
}()
4869
}
4970
wg.Wait()
71+
close(echoServerErr)
72+
for err := range echoServerErr {
73+
t.Error(err)
74+
}
5075
}
5176

5277
func TestClientContextDeadline(t *testing.T) {
@@ -56,53 +81,70 @@ func TestClientContextDeadline(t *testing.T) {
5681
clientConn, serverConn := net.Pipe()
5782
c := &Client{Conn: clientConn}
5883
s := &Server{Conn: serverConn}
59-
go echoServer(t, ctx, s)
84+
echoServerErr := make(chan error)
85+
86+
go echoServer(ctx, s, echoServerErr)
6087

6188
wantErr := errors.New("test deadline exceeded")
6289
ctx, cancel2 := context.WithDeadlineCause(ctx, time.Now(), wantErr)
6390
defer cancel2()
6491

6592
_, err := c.ExchangeDataUnit(ctx, []byte("hello"))
6693
if err != wantErr {
67-
t.Errorf("ExchangeDataUnit(): err == %v, expected %v", err, wantErr)
68-
t.Fail()
94+
echoServerErr <- fmt.Errorf("ExchangeDataUnit(): err == %w, expected %w", err, wantErr)
95+
}
96+
close(echoServerErr)
97+
for err := range echoServerErr {
98+
t.Error(err)
6999
}
70100
}
71101

72-
func TestClientContextCancelled(t *testing.T) {
73-
wantErr := errors.New("client context canceled")
102+
func TestClientContextCanceled(t *testing.T) {
103+
wantErr := errCtxCanceled
74104
ctx, cancel := context.WithCancelCause(context.Background())
75105
cancel(wantErr)
76106

77107
clientConn, serverConn := net.Pipe()
78108
c := &Client{Conn: clientConn}
79109
s := &Server{Conn: serverConn}
80-
go echoServer(t, ctx, s)
110+
echoServerErr := make(chan error)
111+
112+
go echoServer(ctx, s, echoServerErr)
81113

82114
_, err := c.ExchangeDataUnit(ctx, []byte("hello"))
83115
if err != wantErr {
84-
t.Errorf("ExchangeDataUnit(): err == %v, expected %v", err, wantErr)
85-
t.Fail()
116+
echoServerErr <- fmt.Errorf("ExchangeDataUnit(): err == %w, expected %w", err, wantErr)
117+
}
118+
close(echoServerErr)
119+
for err := range echoServerErr {
120+
t.Error(err)
86121
}
87122
}
88123

89-
func TestServerContextCancelled(t *testing.T) {
124+
func TestServerContextCanceled(t *testing.T) {
90125
ctx, cancel := context.WithCancelCause(context.Background())
91126
defer cancel(errTestDone)
92127

93128
clientConn, serverConn := net.Pipe()
94129
c := &Client{Conn: clientConn}
95130
s := &Server{Conn: serverConn}
96131

97-
go c.ExchangeDataUnit(ctx, []byte("hello"))
132+
go func() {
133+
_, _ = c.ExchangeDataUnit(ctx, []byte("hello"))
134+
}()
98135

99136
wantErr := errors.New("server context canceled")
100137
serverCtx, cancel := context.WithCancelCause(context.Background())
101138
cancel(wantErr)
102139

103-
err := echoServer(t, serverCtx, s)
104-
if err != wantErr {
105-
t.Errorf("echoServer(): err == %v, expected %v", err, wantErr)
140+
echoServerErr := make(chan error)
141+
142+
go echoServer(serverCtx, s, echoServerErr)
143+
144+
for err := range echoServerErr {
145+
if err != wantErr {
146+
t.Errorf("unexpected error: %s", err)
147+
}
106148
}
107149
}
108150

@@ -114,7 +156,9 @@ func TestMultipleResponseError(t *testing.T) {
114156
c := &Client{Conn: clientConn}
115157
s := &Server{Conn: serverConn}
116158

117-
go c.ExchangeDataUnit(ctx, []byte("hello"))
159+
go func() {
160+
_, _ = c.ExchangeDataUnit(ctx, []byte("hello"))
161+
}()
118162

119163
req, r, err := s.ServeDataUnit(ctx)
120164
if err != nil {
@@ -133,18 +177,20 @@ func TestMultipleResponseError(t *testing.T) {
133177

134178
// echoServer implements a rudimentary EPP data unit server that echoes
135179
// back each received request.
136-
func echoServer(t *testing.T, ctx context.Context, s *Server) error {
180+
func echoServer(ctx context.Context, s *Server, echoServerErr chan<- error) {
137181
ctx, cancel := context.WithCancelCause(ctx)
138182
defer cancel(errTestDone)
139183

140184
sem := make(chan struct{}, 10)
141185
for {
142-
if t.Failed() {
143-
return errTestFailed
144-
}
145186
err := context.Cause(ctx)
146187
if err != nil {
147-
return err
188+
if err == errTestDone || err == errCtxCanceled {
189+
return
190+
}
191+
echoServerErr <- err
192+
close(echoServerErr)
193+
return
148194
}
149195
sem <- struct{}{}
150196
go func() {
@@ -153,28 +199,31 @@ func echoServer(t *testing.T, ctx context.Context, s *Server) error {
153199
reqCtx, cancel := context.WithCancel(ctx)
154200
defer cancel()
155201

156-
req, r, err := s.ServeDataUnit(reqCtx)
202+
data, r, err := s.ServeDataUnit(reqCtx)
157203
if err != nil {
158204
if err == errTestDone {
159205
return
160206
}
161-
t.Errorf("echoServer: ServeDataUnit(): err == %v", err)
207+
echoServerErr <- fmt.Errorf("echoServer: ServeDataUnit(): err == %w", err)
208+
return
162209
}
163210
time.Sleep(randDuration(10 * time.Millisecond))
164-
err = r.RespondDataUnit(reqCtx, req)
211+
err = r.RespondDataUnit(reqCtx, data)
165212
if err != nil {
166213
if err == errTestDone {
167214
return
168215
}
169-
t.Errorf("echoServer: WriteDataUnit(): err == %v", err)
216+
echoServerErr <- fmt.Errorf("echoServer: RespondDataUnit(): err == %w", err)
170217
}
171218
}()
172219
}
173220
}
174221

175222
func randDuration(max time.Duration) time.Duration {
176-
return time.Duration(rand.Int63n(int64(max)))
223+
return time.Duration(rand.N(max))
177224
}
178225

179-
var errTestDone = errors.New("test done")
180-
var errTestFailed = errors.New("test failed")
226+
var (
227+
errCtxCanceled = errors.New("context canceled")
228+
errTestDone = errors.New("test done")
229+
)

0 commit comments

Comments
 (0)