Skip to content

Commit 4d5caf9

Browse files
authored
Do not call t.Fatal() from non-main goroutines (#291)
Calling t.Fatal() only stops the current goroutine, but not the rest. The result is different depending on a test, but in the worst case scenario (like an error in the server part of a test) the client may hang waiting indefinitely for the response, and the test ends with a panic from the test timeout, without showing the real reason for the issue.
1 parent a3819a7 commit 4d5caf9

File tree

4 files changed

+169
-98
lines changed

4 files changed

+169
-98
lines changed

failover_test.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package desync
22

33
import (
4+
"context"
45
"crypto/rand"
5-
"sync"
66
"sync/atomic"
77
"testing"
88
"time"
99

1010
"github.com/pkg/errors"
11+
"golang.org/x/sync/errgroup"
1112
)
1213

1314
func TestFailoverMissingChunk(t *testing.T) {
@@ -77,48 +78,46 @@ func TestFailoverMutliple(t *testing.T) {
7778
g := NewFailoverGroup(storeA, storeB)
7879

7980
var (
80-
wg sync.WaitGroup
81-
done = make(chan struct{})
82-
timeout = time.After(time.Second)
83-
failOver = time.Tick(10 * time.Millisecond)
81+
ctx, cancel = context.WithTimeout(t.Context(), time.Second)
82+
eg, gCtx = errgroup.WithContext(ctx)
83+
failOver = time.Tick(10 * time.Millisecond)
8484
)
85+
defer cancel()
8586

8687
// Run several goroutines querying the group in a tight loop
8788
for i := 0; i < 16; i++ {
88-
wg.Add(1)
89-
go func() {
89+
eg.Go(func() error {
9090
var id ChunkID
9191
for {
9292
time.Sleep(time.Millisecond)
9393
select {
94-
case <-done:
95-
wg.Done()
96-
return
94+
case <-gCtx.Done():
95+
return nil
9796
default:
9897
rand.Read(id[:])
9998
if _, err := g.GetChunk(id); err != nil {
100-
t.Fatal(err)
99+
return err
101100
}
102101
}
103102
}
104-
}()
103+
})
105104
}
106105

107106
// Make the stores fail over every 10 ms
108-
go func() {
109-
wg.Add(1)
107+
eg.Go(func() error {
110108
for {
111109
select {
112-
case <-timeout: // done running
113-
close(done)
114-
wg.Done()
115-
return
110+
case <-gCtx.Done(): // done running
111+
return nil
116112
case <-failOver: // switch over to the other store
117113
newX := (x + 1) % 2
118114
atomic.StoreInt64(&x, newX)
119115
}
120116
}
121-
}()
117+
})
122118

123-
wg.Wait()
119+
err := eg.Wait()
120+
if err != nil {
121+
t.Fatal(err)
122+
}
124123
}

mount-index_linux_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,18 @@ func TestMountIndex(t *testing.T) {
5555
}()
5656

5757
// Start the Fuse mount
58+
c := make(chan error, 1)
5859
go func() {
5960
ifs := NewIndexMountFS(index, "blob1", s)
60-
MountIndex(ctx, index, ifs, mnt, s, 10)
61+
c <- MountIndex(ctx, index, ifs, mnt, s, 10)
6162
wg.Done()
6263
}()
6364

64-
time.Sleep(time.Second)
65+
select {
66+
case err = <-c:
67+
t.Fatal(err)
68+
case <-time.After(time.Second):
69+
}
6570

6671
// Calculate the hash of the file in the mount point
6772
b, err = ioutil.ReadFile(filepath.Join(mnt, "blob1"))

protocol_test.go

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ package desync
22

33
import (
44
"bytes"
5+
"context"
6+
"errors"
57
"io"
68
"testing"
9+
10+
"golang.org/x/sync/errgroup"
711
)
812

913
func TestProtocol(t *testing.T) {
@@ -19,51 +23,74 @@ func TestProtocol(t *testing.T) {
1923
compressed, _ := Compressor{}.toStorage(uncompressed)
2024
cID := inChunk.ID()
2125

26+
ctx, cancel := context.WithCancel(t.Context())
27+
g, gCtx := errgroup.WithContext(ctx)
28+
defer cancel()
29+
2230
// Server
23-
go func() {
31+
g.Go(func() error {
2432
flags, err := client.Initialize(CaProtocolReadableStore)
2533
if err != nil {
26-
t.Fatal(err)
34+
return err
2735
}
2836
if flags&CaProtocolPullChunks == 0 {
29-
t.Fatalf("client not asking for chunks")
37+
return errors.New("client not asking for chunks")
3038
}
3139
for {
3240
m, err := client.ReadMessage()
3341
if err != nil {
34-
t.Fatal(err)
42+
if errors.Is(ctx.Err(), context.Canceled) {
43+
return nil
44+
}
45+
return err
3546
}
3647
switch m.Type {
3748
case CaProtocolRequest:
3849
id, err := ChunkIDFromSlice(m.Body[8:40])
3950
if err != nil {
40-
t.Fatal(err)
51+
return err
4152
}
4253
if err := client.SendProtocolChunk(id, 0, compressed); err != nil {
43-
t.Fatal(err)
54+
return err
4455
}
4556
default:
4657

47-
t.Fatal("unexpected message")
58+
return errors.New("unexpected message")
4859
}
4960
}
50-
}()
61+
})
5162

5263
// Client
53-
flags, err := server.Initialize(CaProtocolPullChunks)
54-
if err != nil {
55-
t.Fatal(err)
56-
}
57-
if flags&CaProtocolReadableStore == 0 {
58-
t.Fatalf("server not offering chunks")
59-
}
64+
g.Go(func() error {
65+
defer cancel()
66+
flags, err := server.Initialize(CaProtocolPullChunks)
67+
if err != nil {
68+
return err
69+
}
70+
if flags&CaProtocolReadableStore == 0 {
71+
return errors.New("server not offering chunks")
72+
}
73+
74+
chunk, err := server.RequestChunk(cID)
75+
if err != nil {
76+
return err
77+
}
78+
b, _ := chunk.Data()
79+
if !bytes.Equal(b, uncompressed) {
80+
return errors.New("chunk data doesn't match expected")
81+
}
82+
return nil
83+
})
6084

61-
chunk, err := server.RequestChunk(cID)
85+
<-gCtx.Done()
86+
// unblock client/server in case of an error
87+
r1.Close()
88+
r2.Close()
89+
w1.Close()
90+
w2.Close()
91+
92+
err := g.Wait()
6293
if err != nil {
6394
t.Fatal(err)
6495
}
65-
b, _ := chunk.Data()
66-
if !bytes.Equal(b, uncompressed) {
67-
t.Fatal("chunk data doesn't match expected")
68-
}
6996
}

0 commit comments

Comments
 (0)