fork of https://github.com/sourcegraph/zoekt
1package search
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/google/go-cmp/cmp"
9)
10
11func BenchmarkYield(b *testing.B) {
12 // Use quantum longer than the benchmark runs
13 quantum := time.Minute
14
15 // Benchmark of the raw primitive we are using to tell if we should yield.
16 b.Run("timer", func(b *testing.B) {
17 t := time.NewTimer(quantum)
18 defer t.Stop()
19
20 for n := 0; n < b.N; n++ {
21 select {
22 case <-t.C:
23 b.Fatal("done")
24 default:
25 }
26 }
27 })
28
29 // Benchmark of an alternative approach to timer. It is _much_ slower.
30 b.Run("now", func(b *testing.B) {
31 deadline := time.Now().Add(quantum)
32
33 for n := 0; n < b.N; n++ {
34 if time.Now().After(deadline) {
35 b.Fatal("done")
36 }
37 }
38 })
39
40 // Benchmark of our wrapper around time.Timer
41 b.Run("deadlineTimer", func(b *testing.B) {
42 t := newDeadlineTimer(time.Now().Add(quantum))
43 defer t.Stop()
44
45 for n := 0; n < b.N; n++ {
46 if t.Exceeded() {
47 b.Fatal("done")
48 }
49 }
50 })
51
52 // Bencmark of actual yield function
53 b.Run("yield", func(b *testing.B) {
54 ctx := context.Background()
55 sched := newMultiScheduler(1)
56 sched.interactiveDuration = quantum
57 proc, err := sched.Acquire(ctx)
58 if err != nil {
59 b.Fatal(err)
60 }
61 defer proc.Release()
62
63 for n := 0; n < b.N; n++ {
64 if err := proc.Yield(ctx); err != nil {
65 b.Fatal(err)
66 }
67 }
68 })
69}
70
71func TestYield(t *testing.T) {
72 ctx := context.Background()
73 quantum := 10 * time.Millisecond
74 deadline := time.Now().Add(quantum)
75
76 sched := newMultiScheduler(1)
77 sched.interactiveDuration = quantum
78 proc, err := sched.Acquire(ctx)
79 if err != nil {
80 t.Fatal(err)
81 }
82 defer proc.Release()
83
84 called := false
85 oldYieldFunc := proc.yieldFunc
86 proc.yieldFunc = func(ctx context.Context) error {
87 if called {
88 t.Fatal("yieldFunc called more than once")
89 }
90 called = true
91 if time.Now().Before(deadline) {
92 t.Fatal("yieldFunc called before deadline")
93 }
94 return oldYieldFunc(ctx)
95 }
96
97 var pre, post int
98 for post < 10 {
99 if err := proc.Yield(ctx); err != nil {
100 t.Fatal(err)
101 }
102
103 if called {
104 post++
105 } else {
106 pre++
107 }
108 }
109
110 // We can't assert anything based on time since it will run into race
111 // conditions with the runtime. So we just log the pre and post values so we
112 // can eyeball them sometimes :)
113 t.Logf("pre=%d post=%d", pre, post)
114}
115
116func TestMultiScheduler(t *testing.T) {
117 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
118 defer cancel()
119
120 capacity := 8
121 batchCap := capacity / 4
122 sched := newMultiScheduler(int64(capacity))
123 sched.interactiveDuration = 0 // instantly downgrade to batch on call to yield.
124
125 var procs []*process
126 addProc := func() {
127 t.Helper()
128 proc, err := sched.Acquire(ctx)
129 if err != nil {
130 t.Fatal(err)
131 }
132 procs = append(procs, proc)
133 }
134 defer func() {
135 for _, p := range procs {
136 p.Release()
137 }
138 }()
139
140 // Fill up interactive queue
141 for range capacity {
142 addProc()
143 }
144
145 // We expect this to fail since the queue is at capacity
146 if _, err := sched.Acquire(quickCtx(t)); err == nil {
147 t.Fatal("expected first acquire after cap to fail")
148 }
149
150 // move procs[0] to batch queue freeing up interactive
151 if err := procs[0].Yield(ctx); err != nil {
152 t.Fatal(err)
153 }
154 addProc()
155
156 // We expect this to fail since the queue is at capacity again.
157 if _, err := sched.Acquire(quickCtx(t)); err == nil {
158 t.Fatal("expected second acquire after cap to fail")
159 }
160
161 // Fill up batch queue. Already has one item
162 for i := 1; i < batchCap; i++ {
163 if err := procs[i].Yield(ctx); err != nil {
164 t.Fatal(err)
165 }
166 }
167
168 // We expect this to fail since the batch queue is at capacity.
169 if err := procs[batchCap].Yield(quickCtx(t)); err == nil {
170 t.Fatal("expected second acquire after cap to fail")
171 }
172
173 for _, p := range procs {
174 p.Release()
175 }
176 procs = nil
177}
178
179func quickCtx(t *testing.T) context.Context {
180 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
181 t.Cleanup(cancel)
182 return ctx
183}
184
185func TestParseTuneables(t *testing.T) {
186 cases := map[string]map[string]int{
187 "": {},
188 "disable": {"disable": 1},
189 "disable,batchdiv=2": {"disable": 1, "batchdiv": 2},
190 }
191
192 for v, want := range cases {
193 got := parseTuneables(v)
194 if d := cmp.Diff(want, got); d != "" {
195 t.Errorf("parseTuneables(%q) mismatch (-want, +got):\n%s", v, d)
196 }
197 }
198}