Skip to content

Commit 28add60

Browse files
committed
add test to detect race condition due to slower fetch ops
1 parent 90ddfae commit 28add60

File tree

3 files changed

+148
-3
lines changed

3 files changed

+148
-3
lines changed

.github/workflows/test.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ jobs:
3030
run: |
3131
go test -v -cover -race -count 1 -timeout 20s --tags deadlock_test -run Test_mirror_detect_race_clone ./pkg/mirror/...
3232
go test -v -cover -race -count 1 -timeout 60s --tags deadlock_test -run Test_mirror_detect_race_repo_pool ./pkg/mirror/...
33+
go test -v -cover -race -count 1 -timeout 240s --tags deadlock_test -run Test_mirror_detect_race_slow_fetch ./pkg/mirror/...

pkg/mirror/z_e2e_race_test.go

+138-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package mirror
55
import (
66
"context"
77
"os"
8+
"os/exec"
9+
"path"
810
"path/filepath"
11+
"slices"
912
"sync"
1013
"testing"
1114
"time"
@@ -61,7 +64,6 @@ func Test_mirror_detect_race_clone(t *testing.T) {
6164
defer wg.Done()
6265
if err := repo.Mirror(ctx); err != nil {
6366
t.Error("unable to mirror", "err", err)
64-
os.Exit(1)
6567
}
6668

6769
assertLinkedFile(t, root, link1, "file", testName+"-2")
@@ -83,7 +85,6 @@ func Test_mirror_detect_race_clone(t *testing.T) {
8385
defer wg.Done()
8486
if err := repo.Mirror(ctx); err != nil {
8587
t.Error("unable to mirror error", "err", err)
86-
os.Exit(1)
8788
}
8889
}()
8990

@@ -94,7 +95,7 @@ func Test_mirror_detect_race_clone(t *testing.T) {
9495
defer os.RemoveAll(tempClone)
9596

9697
if cloneSHA, err := repo.Clone(ctx, tempClone, testMainBranch, nil, i%2 == 0); err != nil {
97-
t.Fatalf("unexpected error %s", err)
98+
t.Errorf("unexpected error %s", err)
9899
} else {
99100
if cloneSHA != fileSHA2 {
100101
t.Errorf("clone sha mismatch got:%s want:%s", cloneSHA, fileSHA2)
@@ -108,6 +109,138 @@ func Test_mirror_detect_race_clone(t *testing.T) {
108109

109110
}
110111

112+
func Test_mirror_detect_race_slow_fetch(t *testing.T) {
113+
// replace global git path with slower git wrapper script
114+
cwd, _ := os.Getwd()
115+
gitExecutablePath = exec.Command(path.Join(cwd, "z_git_slow_fetch.sh")).String()
116+
117+
testTmpDir := mustTmpDir(t)
118+
defer os.RemoveAll(testTmpDir)
119+
120+
ctx, cancel := context.WithCancel(context.TODO())
121+
defer cancel()
122+
123+
upstream := filepath.Join(testTmpDir, testUpstreamRepo)
124+
root := filepath.Join(testTmpDir, testRoot)
125+
126+
testName := t.Name()
127+
128+
t.Log("TEST-1: init upstream")
129+
fileSHA1 := mustInitRepo(t, upstream, "file", testName+"-1")
130+
131+
repo := mustCreateRepoAndMirror(t, upstream, root, "", "")
132+
repo.mirrorTimeout = 2 * time.Minute
133+
134+
// verify checkout files
135+
assertCommitLog(t, repo, "HEAD", "", fileSHA1, testName+"-1", []string{"file"})
136+
137+
// start mirror loop
138+
go repo.StartLoop(ctx)
139+
close(repo.stop)
140+
141+
t.Run("slow-fetch-without-timeout", func(t *testing.T) {
142+
143+
// all following assertions will always be true
144+
// this test is about testing deadlocks and detecting race conditions
145+
// due to background ctx following assertions should always succeed
146+
for range 3 {
147+
wg := &sync.WaitGroup{}
148+
wg.Add(1)
149+
go func() {
150+
defer wg.Done()
151+
if err := repo.Mirror(ctx); err != nil {
152+
t.Error("unable to mirror error", "err", err)
153+
}
154+
}()
155+
156+
wg.Add(1)
157+
go func() {
158+
defer wg.Done()
159+
160+
ctx := context.Background()
161+
162+
time.Sleep(time.Second) // wait for repo.Mirror to grab lock
163+
164+
gotHash, err := repo.Hash(ctx, "HEAD", "")
165+
if err != nil {
166+
t.Errorf("unexpected error: %v", err)
167+
} else if gotHash != fileSHA1 {
168+
t.Errorf("ref '%s' on path '%s' SHA mismatch got:%s want:%s", "HEAD", "", gotHash, fileSHA1)
169+
}
170+
171+
if got, err := repo.Subject(ctx, fileSHA1); err != nil {
172+
t.Errorf("unexpected error: %v", err)
173+
} else if got != testName+"-1" {
174+
t.Errorf("subject mismatch sha:%s got:%s want:%s", gotHash, got, testName+"-1")
175+
}
176+
177+
if got, err := repo.ChangedFiles(ctx, fileSHA1); err != nil {
178+
t.Errorf("unexpected error: %v", err)
179+
} else if slices.Compare(got, []string{"file"}) != 0 {
180+
t.Errorf("changed file mismatch sha:%s got:%s want:%s", gotHash, got, []string{"file"})
181+
}
182+
}()
183+
wg.Wait()
184+
}
185+
})
186+
187+
t.Run("slow-fetch-with-client-timeout", func(t *testing.T) {
188+
// all following assertions will always be true
189+
// this test is about testing deadlocks and detecting race conditions
190+
// due to shorter ctx timeout Hash, Subject and ChangedFiles should always fail
191+
for range 3 {
192+
wg := &sync.WaitGroup{}
193+
wg.Add(1)
194+
go func() {
195+
defer wg.Done()
196+
if err := repo.Mirror(ctx); err != nil {
197+
t.Error("unable to mirror error", "err", err)
198+
}
199+
}()
200+
201+
wg.Add(1)
202+
go func() {
203+
defer wg.Done()
204+
time.Sleep(time.Second) // wait for repo.Mirror to grab lock
205+
206+
ctx1, cancel1 := context.WithTimeout(context.Background(), 5*time.Second)
207+
if _, err := repo.Hash(ctx1, "HEAD", ""); err == nil {
208+
t.Errorf("error was expected due to sorter timeout")
209+
}
210+
211+
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
212+
if _, err := repo.Subject(ctx2, fileSHA1); err == nil {
213+
t.Errorf("error was expected due to sorter timeout")
214+
}
215+
216+
ctx3, cancel3 := context.WithTimeout(context.Background(), 5*time.Second)
217+
if _, err := repo.ChangedFiles(ctx3, fileSHA1); err == nil {
218+
t.Errorf("error was expected due to sorter timeout")
219+
}
220+
221+
cancel1()
222+
cancel2()
223+
cancel3()
224+
}()
225+
wg.Wait()
226+
}
227+
})
228+
229+
t.Run("slow-fetch-with-mirror-timeout", func(t *testing.T) {
230+
for range 3 {
231+
ctx1, cancel1 := context.WithTimeout(context.Background(), 5*time.Second)
232+
233+
start := time.Now()
234+
repo.Mirror(ctx1)
235+
// 5s timeout + 5s waitDelay + 2s buffer
236+
if time.Since(start) > (5+5+2)*time.Second {
237+
t.Errorf("error: mirror function should have existed after ctx timeout but it took %s", time.Since(start))
238+
}
239+
cancel1()
240+
}
241+
})
242+
}
243+
111244
func Test_mirror_detect_race_repo_pool(t *testing.T) {
112245
testTmpDir := mustTmpDir(t)
113246
defer os.RemoveAll(testTmpDir)
@@ -146,6 +279,7 @@ func Test_mirror_detect_race_repo_pool(t *testing.T) {
146279
t.Log("adding remote1", "count", i)
147280
readStopped := make(chan bool)
148281
ctx, cancel := context.WithCancel(t.Context())
282+
defer cancel()
149283

150284
newConfig := RepoPoolConfig{
151285
Defaults: DefaultConfig{
@@ -215,6 +349,7 @@ func Test_mirror_detect_race_repo_pool(t *testing.T) {
215349
t.Log("adding remote2", "count", i)
216350
readStopped := make(chan bool)
217351
ctx, cancel := context.WithCancel(t.Context())
352+
defer cancel()
218353

219354
newConfig := RepoPoolConfig{
220355
Defaults: DefaultConfig{

pkg/mirror/z_git_slow_fetch.sh

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
3+
if [ "$1" = "fetch" ]; then
4+
sleep 20
5+
git "$@"
6+
exit $?
7+
fi
8+
9+
git "$@"

0 commit comments

Comments
 (0)