-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlimiter_test.go
133 lines (115 loc) · 2.82 KB
/
limiter_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package hoglet_test
import (
"context"
"sync"
"testing"
"time"
"github.com/exaring/hoglet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockPanickingObservable struct{}
func (mo mockPanickingObservable) Observe(shouldPanic bool) {
// abuse the observer interface to signal a panic
if shouldPanic {
panic("mockObservable meant to panic")
}
}
type mockObserverFactory struct{}
func (mof mockObserverFactory) ObserverForCall(ctx context.Context, state hoglet.State) (hoglet.Observer, error) {
return &mockPanickingObservable{}, nil
}
func Test_ConcurrencyLimiter(t *testing.T) {
type args struct {
limit int64
block bool
}
tests := []struct {
name string
args args
calls int
cancel bool
wantPanicOn *int // which call to panic on (if at all)
wantErr error
}{
{
name: "under limit",
args: args{limit: 1, block: false},
calls: 0,
wantErr: nil,
},
{
name: "over limit; non-blocking",
args: args{limit: 1, block: false},
calls: 1,
wantErr: hoglet.ErrConcurrencyLimitReached,
},
{
name: "on limit; blocking",
args: args{limit: 1, block: true},
calls: 1,
cancel: true, // cancel simulates a timeout in this case
wantErr: hoglet.ErrWaitingForSlot,
},
{
name: "cancellation releases with error",
args: args{limit: 1, block: true},
calls: 1,
cancel: true,
wantErr: context.Canceled,
},
{
name: "panic releases",
args: args{limit: 1, block: true},
calls: 1,
cancel: false,
wantPanicOn: ptr(0),
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctxCalls, cancelCalls := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancelCalls()
wgStart := &sync.WaitGroup{}
wgStop := &sync.WaitGroup{}
defer wgStop.Wait()
cl := hoglet.ConcurrencyLimiter(tt.args.limit, tt.args.block)
of, err := cl.Wrap(mockObserverFactory{})
require.NoError(t, err)
for i := 0; i < tt.calls; i++ {
wantPanic := tt.wantPanicOn != nil && *tt.wantPanicOn == i
f := func() {
defer wgStop.Done()
o, err := of.ObserverForCall(ctxCalls, hoglet.StateClosed)
wgStart.Done()
require.NoError(t, err)
<-ctxCalls.Done()
o.Observe(wantPanic)
}
wgStart.Add(1)
wgStop.Add(1)
if wantPanic {
go assert.Panics(t, f)
} else {
go f()
}
}
ctx, cancel := context.WithCancel(context.Background())
if tt.cancel {
cancel()
} else {
defer cancel()
}
wgStart.Wait() // ensure all calls are started
o, err := of.ObserverForCall(ctx, hoglet.StateClosed)
assert.ErrorIs(t, err, tt.wantErr)
if tt.wantErr == nil {
assert.NotNil(t, o)
}
})
}
}
func ptr[T any](in T) *T {
return &in
}