-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathalloc_test.go
More file actions
297 lines (260 loc) · 7.8 KB
/
Copy pathalloc_test.go
File metadata and controls
297 lines (260 loc) · 7.8 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
package topic_test
import (
"testing"
"github.com/ardnew/topic"
)
// payload is deliberately larger than a word and contains no pointers, so
// placing it in an interface would allocate. Any path below that reports zero
// allocations therefore never boxed the value.
type payload struct{ a, b, c, d int64 }
type reading struct{ v float64 }
type meter interface{ read() float64 }
func (p *payload) read() float64 { return float64(p.a) }
// allocs reports allocations per publication, draining the subscription each
// round so that saturation never hides work.
func allocs(t *testing.T, publish func()) float64 {
t.Helper()
return testing.AllocsPerRun(1000, publish)
}
func TestAllocsNoSubscribers(t *testing.T) {
var b topic.Broker
v := payload{a: 1}
if got := allocs(t, func() { b.Publish(v) }); got != 0 {
t.Errorf("publish with no subscribers: %v allocs, want 0", got)
}
}
func TestAllocsAfterAllSubscriptionsCancelled(t *testing.T) {
var b topic.Broker
_, cancel := b.Subscribe[payload]()
cancel()
v := payload{a: 1}
if got := allocs(t, func() { b.Publish(v) }); got != 0 {
t.Errorf("publish after cancellation: %v allocs, want 0", got)
}
}
func TestAllocsDirectIdenticalType(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(topic.Buffer[payload](1))
defer cancel()
v := payload{a: 1}
got := allocs(t, func() {
b.Publish(v)
<-ch
})
if got != 0 {
t.Errorf("direct delivery: %v allocs, want 0", got)
}
}
func TestAllocsFanOutIdenticalType(t *testing.T) {
const subs = 16
var b topic.Broker
chans := make([]<-chan payload, subs)
for i := range chans {
ch, cancel := b.Subscribe(topic.Buffer[payload](1))
defer cancel()
chans[i] = ch
}
v := payload{a: 1}
got := allocs(t, func() {
b.Publish(v)
for _, ch := range chans {
<-ch
}
})
if got != 0 {
t.Errorf("fan-out to %d subscriptions: %v allocs, want 0", subs, got)
}
}
func TestAllocsPointerToInterfaceSubscription(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(topic.Buffer[meter](1))
defer cancel()
p := &payload{a: 1}
got := allocs(t, func() {
b.Publish(p)
<-ch
})
if got != 0 {
t.Errorf("pointer to interface subscription: %v allocs, want 0", got)
}
}
func TestAllocsInterfaceValueToInterfaceSubscription(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(topic.Buffer[meter](1))
defer cancel()
var m meter = &payload{a: 1}
got := allocs(t, func() {
b.Publish(m)
<-ch
})
if got != 0 {
t.Errorf("interface value to interface subscription: %v allocs, want 0", got)
}
}
func TestAllocsErrorValueToErrorSubscription(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(topic.Buffer[error](1))
defer cancel()
var err error = &failure{code: 1}
got := allocs(t, func() {
b.Publish(err)
<-ch
})
if got != 0 {
t.Errorf("error value to error subscription: %v allocs, want 0", got)
}
}
func TestAllocsDirectlyMatchedTransformation(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(
topic.Buffer[reading](1),
topic.From(func(p payload) (reading, bool) { return reading{v: float64(p.a)}, true }),
)
defer cancel()
v := payload{a: 1}
got := allocs(t, func() {
b.Publish(v)
<-ch
})
if got != 0 {
t.Errorf("directly matched transformation: %v allocs, want 0", got)
}
}
func TestAllocsDirectlyMatchedFilter(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(
topic.Buffer[payload](1),
topic.From(func(p payload) (payload, bool) { return p, p.a > 0 }),
)
defer cancel()
pass := payload{a: 1}
drop := payload{a: -1}
if got := allocs(t, func() { b.Publish(drop) }); got != 0 {
t.Errorf("rejected by filter: %v allocs, want 0", got)
}
got := allocs(t, func() {
b.Publish(pass)
<-ch
})
if got != 0 {
t.Errorf("accepted by filter: %v allocs, want 0", got)
}
}
func TestAllocsSaturatedSubscription(t *testing.T) {
var b topic.Broker
_, cancel := b.Subscribe(topic.Buffer[payload](1))
defer cancel()
v := payload{a: 1}
b.Publish(v) // fill the buffer; every later publication is dropped
if got := allocs(t, func() { b.Publish(v) }); got != 0 {
t.Errorf("publish to a saturated subscription: %v allocs, want 0", got)
}
}
// TestAllocsBoxedPathIsIndependentOfFanOut pins the documented cost of the one
// publication path that does allocate: a value that is neither pointer-shaped
// nor already an interface, offered to interface subscriptions. It must cost
// exactly one allocation no matter how many subscriptions match.
func TestAllocsBoxedPathIsIndependentOfFanOut(t *testing.T) {
for _, subs := range []int{1, 2, 16} {
var b topic.Broker
chans := make([]<-chan any, subs)
for i := range chans {
ch, cancel := b.Subscribe(topic.Buffer[any](1))
defer cancel()
chans[i] = ch
}
v := payload{a: 1}
got := allocs(t, func() {
b.Publish(v)
for _, ch := range chans {
<-ch
}
})
if got != 1 {
t.Errorf("%d any subscriptions: %v allocs, want exactly 1", subs, got)
}
}
}
// TestAllocsSmallValueToAnySubscription records that the boxing allocation is
// a cost of the value, not of the broker. The runtime keeps the first 256
// integers in read-only storage and boxes any one-, two-, four-, or
// eight-byte pointer-free value whose bits fall in that range by pointing at
// it, so such a publication reaches an interface subscription for free.
//
// This is a property of the Go runtime rather than a promise of this package.
// If it ever stops holding, the performance table in README.md is wrong and
// this test is how that is discovered.
func TestAllocsSmallValueToAnySubscription(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(topic.Buffer[any](1))
defer cancel()
v := tick{n: 1} // one word, no pointers, and 1 < 256
got := allocs(t, func() {
b.Publish(v)
<-ch
})
if got != 0 {
t.Errorf("small value to an any subscription: %v allocs, want 0", got)
}
// The same type carrying a number outside that range must box normally,
// which is what makes the result above a statement about the value.
big := tick{n: 256}
if got := allocs(t, func() {
b.Publish(big)
<-ch
}); got != 1 {
t.Errorf("large value to an any subscription: %v allocs, want exactly 1", got)
}
}
// TestAllocsMixedWorlds records that the typed path's zero-allocation
// guarantee is a property of the broker, not of the publication alone: a
// single interface subscription anywhere on the broker forces the one
// documented boxing allocation for every publication of a value that is
// neither pointer-shaped nor already an interface. The count stays at one
// however many subscriptions of either kind match.
func TestAllocsMixedWorlds(t *testing.T) {
for _, n := range []int{1, 4, 16} {
var b topic.Broker
typed := make([]<-chan payload, n)
boxed := make([]<-chan any, n)
for i := range n {
ch, cancel := b.Subscribe(topic.Buffer[payload](1))
defer cancel()
typed[i] = ch
anyCh, cancelAny := b.Subscribe(topic.Buffer[any](1))
defer cancelAny()
boxed[i] = anyCh
}
v := payload{a: 1}
got := allocs(t, func() {
b.Publish(v)
for i := range n {
<-typed[i]
<-boxed[i]
}
})
if got != 1 {
t.Errorf("%d typed and %d any subscriptions: %v allocs, want exactly 1", n, n, got)
}
}
}
// TestAllocsDynamicSubscriptionConcreteSource covers the one dynamic-world
// path that must still be free: a pointer publication reaching a concrete
// source of a subscription that also declares an interface source.
func TestAllocsDynamicSubscriptionConcreteSource(t *testing.T) {
var b topic.Broker
ch, cancel := b.Subscribe(
topic.Buffer[reading](1),
topic.From(func(m meter) (reading, bool) { return reading{v: m.read()}, true }),
topic.From(func(p *payload) (reading, bool) { return reading{v: float64(p.b)}, true }),
)
defer cancel()
p := &payload{b: 2}
got := allocs(t, func() {
b.Publish(p)
<-ch
})
if got != 0 {
t.Errorf("pointer to a dynamic subscription: %v allocs, want 0", got)
}
}