1 package runtime
2
3 import (
4 "bytes"
5 "crypto/rand"
6 "errors"
7 "fmt"
8 "io"
9 "sync/atomic"
10 "testing"
11
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14 )
15
16 func TestByteStreamConsumer(t *testing.T) {
17 const expected = "the data for the stream to be sent over the wire"
18 consumer := ByteStreamConsumer()
19
20 t.Run("can consume as a ReaderFrom", func(t *testing.T) {
21 var dest = &readerFromDummy{}
22 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), dest))
23 assert.Equal(t, expected, dest.b.String())
24 })
25
26 t.Run("can consume as a Writer", func(t *testing.T) {
27 dest := &closingWriter{}
28 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), dest))
29 assert.Equal(t, expected, dest.String())
30 })
31
32 t.Run("can consume as a string", func(t *testing.T) {
33 var dest string
34 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
35 assert.Equal(t, expected, dest)
36 })
37
38 t.Run("can consume as a binary unmarshaler", func(t *testing.T) {
39 var dest binaryUnmarshalDummy
40 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
41 assert.Equal(t, expected, dest.str)
42 })
43
44 t.Run("can consume as a binary slice", func(t *testing.T) {
45 var dest []byte
46 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
47 assert.Equal(t, expected, string(dest))
48 })
49
50 t.Run("can consume as a type, with underlying as a binary slice", func(t *testing.T) {
51 type binarySlice []byte
52 var dest binarySlice
53 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
54 assert.Equal(t, expected, string(dest))
55 })
56
57 t.Run("can consume as a type, with underlying as a string", func(t *testing.T) {
58 type aliasedString string
59 var dest aliasedString
60 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
61 assert.Equal(t, expected, string(dest))
62 })
63
64 t.Run("can consume as an interface with underlying type []byte", func(t *testing.T) {
65 var dest interface{} = []byte{}
66 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
67 asBytes, ok := dest.([]byte)
68 require.True(t, ok)
69 assert.Equal(t, expected, string(asBytes))
70 })
71
72 t.Run("can consume as an interface with underlying type string", func(t *testing.T) {
73 var dest interface{} = "x"
74 require.NoError(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
75 asString, ok := dest.(string)
76 require.True(t, ok)
77 assert.Equal(t, expected, asString)
78 })
79
80 t.Run("with CloseStream option", func(t *testing.T) {
81 t.Run("wants to close stream", func(t *testing.T) {
82 closingConsumer := ByteStreamConsumer(ClosesStream)
83 var dest bytes.Buffer
84 r := &closingReader{b: bytes.NewBufferString(expected)}
85
86 require.NoError(t, closingConsumer.Consume(r, &dest))
87 assert.Equal(t, expected, dest.String())
88 assert.EqualValues(t, 1, r.calledClose)
89 })
90
91 t.Run("don't want to close stream", func(t *testing.T) {
92 nonClosingConsumer := ByteStreamConsumer()
93 var dest bytes.Buffer
94 r := &closingReader{b: bytes.NewBufferString(expected)}
95
96 require.NoError(t, nonClosingConsumer.Consume(r, &dest))
97 assert.Equal(t, expected, dest.String())
98 assert.EqualValues(t, 0, r.calledClose)
99 })
100 })
101
102 t.Run("error cases", func(t *testing.T) {
103 t.Run("passing in a nil slice will result in an error", func(t *testing.T) {
104 var dest *[]byte
105 require.Error(t, consumer.Consume(bytes.NewBufferString(expected), &dest))
106 })
107
108 t.Run("passing a non-pointer will result in an error", func(t *testing.T) {
109 var dest []byte
110 require.Error(t, consumer.Consume(bytes.NewBufferString(expected), dest))
111 })
112
113 t.Run("passing in nil destination result in an error", func(t *testing.T) {
114 require.Error(t, consumer.Consume(bytes.NewBufferString(expected), nil))
115 })
116
117 t.Run("a reader who results in an error, will make it fail", func(t *testing.T) {
118 t.Run("binaryUnmarshal case", func(t *testing.T) {
119 var dest binaryUnmarshalDummy
120 require.Error(t, consumer.Consume(new(nopReader), &dest))
121 })
122
123 t.Run("[]byte case", func(t *testing.T) {
124 var dest []byte
125 require.Error(t, consumer.Consume(new(nopReader), &dest))
126 })
127 })
128
129 t.Run("the reader cannot be nil", func(t *testing.T) {
130 var dest []byte
131 require.Error(t, consumer.Consume(nil, &dest))
132 })
133 })
134 }
135
136 func BenchmarkByteStreamConsumer(b *testing.B) {
137 const bufferSize = 1000
138 expected := make([]byte, bufferSize)
139 _, err := rand.Read(expected)
140 require.NoError(b, err)
141 consumer := ByteStreamConsumer()
142 input := bytes.NewReader(expected)
143
144 b.Run("with writer", func(b *testing.B) {
145 b.ReportAllocs()
146 b.ResetTimer()
147 var dest bytes.Buffer
148 for i := 0; i < b.N; i++ {
149 err = consumer.Consume(input, &dest)
150 if err != nil {
151 b.Fatal(err)
152 }
153 _, _ = input.Seek(0, io.SeekStart)
154 dest.Reset()
155 }
156 })
157 b.Run("with BinaryUnmarshal", func(b *testing.B) {
158 b.ReportAllocs()
159 b.ResetTimer()
160 var dest binaryUnmarshalDummyZeroAlloc
161 for i := 0; i < b.N; i++ {
162 err = consumer.Consume(input, &dest)
163 if err != nil {
164 b.Fatal(err)
165 }
166 _, _ = input.Seek(0, io.SeekStart)
167 }
168 })
169 b.Run("with string", func(b *testing.B) {
170 b.ReportAllocs()
171 b.ResetTimer()
172 var dest string
173 for i := 0; i < b.N; i++ {
174 err = consumer.Consume(input, &dest)
175 if err != nil {
176 b.Fatal(err)
177 }
178 _, _ = input.Seek(0, io.SeekStart)
179 }
180 })
181 b.Run("with []byte", func(b *testing.B) {
182 b.ReportAllocs()
183 b.ResetTimer()
184 var dest []byte
185 for i := 0; i < b.N; i++ {
186 err = consumer.Consume(input, &dest)
187 if err != nil {
188 b.Fatal(err)
189 }
190 _, _ = input.Seek(0, io.SeekStart)
191 }
192 })
193 b.Run("with aliased string", func(b *testing.B) {
194 b.ReportAllocs()
195 b.ResetTimer()
196 type aliasedString string
197 var dest aliasedString
198 for i := 0; i < b.N; i++ {
199 err = consumer.Consume(input, &dest)
200 if err != nil {
201 b.Fatal(err)
202 }
203 _, _ = input.Seek(0, io.SeekStart)
204 }
205 })
206 b.Run("with aliased []byte", func(b *testing.B) {
207 b.ReportAllocs()
208 b.ResetTimer()
209 type binarySlice []byte
210 var dest binarySlice
211 for i := 0; i < b.N; i++ {
212 err = consumer.Consume(input, &dest)
213 if err != nil {
214 b.Fatal(err)
215 }
216 _, _ = input.Seek(0, io.SeekStart)
217 }
218 })
219 }
220
221 func TestByteStreamProducer(t *testing.T) {
222 const expected = "the data for the stream to be sent over the wire"
223 producer := ByteStreamProducer()
224
225 t.Run("can produce from a WriterTo", func(t *testing.T) {
226 var w bytes.Buffer
227 var data io.WriterTo = bytes.NewBufferString(expected)
228 require.NoError(t, producer.Produce(&w, data))
229 assert.Equal(t, expected, w.String())
230 })
231
232 t.Run("can produce from a Reader", func(t *testing.T) {
233 var w bytes.Buffer
234 var data io.Reader = bytes.NewBufferString(expected)
235 require.NoError(t, producer.Produce(&w, data))
236 assert.Equal(t, expected, w.String())
237 })
238
239 t.Run("can produce from a binary marshaler", func(t *testing.T) {
240 var w bytes.Buffer
241 data := &binaryMarshalDummy{str: expected}
242 require.NoError(t, producer.Produce(&w, data))
243 assert.Equal(t, expected, w.String())
244 })
245
246 t.Run("can produce from a string", func(t *testing.T) {
247 var w bytes.Buffer
248 data := expected
249 require.NoError(t, producer.Produce(&w, data))
250 assert.Equal(t, expected, w.String())
251 })
252
253 t.Run("can produce from a []byte", func(t *testing.T) {
254 var w bytes.Buffer
255 data := []byte(expected)
256 require.NoError(t, producer.Produce(&w, data))
257 assert.Equal(t, expected, w.String())
258 })
259
260 t.Run("can produce from an error", func(t *testing.T) {
261 var w bytes.Buffer
262 data := errors.New(expected)
263 require.NoError(t, producer.Produce(&w, data))
264 assert.Equal(t, expected, w.String())
265 })
266
267 t.Run("can produce from an aliased string", func(t *testing.T) {
268 var w bytes.Buffer
269 type aliasedString string
270 var data aliasedString = expected
271 require.NoError(t, producer.Produce(&w, data))
272 assert.Equal(t, expected, w.String())
273 })
274
275 t.Run("can produce from an interface with underlying type string", func(t *testing.T) {
276 var w bytes.Buffer
277 var data interface{} = expected
278 require.NoError(t, producer.Produce(&w, data))
279 assert.Equal(t, expected, w.String())
280 })
281
282 t.Run("can produce from an aliased []byte", func(t *testing.T) {
283 var w bytes.Buffer
284 type binarySlice []byte
285 var data binarySlice = []byte(expected)
286 require.NoError(t, producer.Produce(&w, data))
287 assert.Equal(t, expected, w.String())
288 })
289
290 t.Run("can produce from an interface with underling type []byte", func(t *testing.T) {
291 var w bytes.Buffer
292 var data interface{} = []byte(expected)
293 require.NoError(t, producer.Produce(&w, data))
294 assert.Equal(t, expected, w.String())
295 })
296
297 t.Run("can produce JSON from an arbitrary struct", func(t *testing.T) {
298 var w bytes.Buffer
299 type dummy struct {
300 Message string `json:"message,omitempty"`
301 }
302 data := dummy{Message: expected}
303 require.NoError(t, producer.Produce(&w, data))
304 assert.Equal(t, fmt.Sprintf(`{"message":%q}`, expected), w.String())
305 })
306
307 t.Run("can produce JSON from a pointer to an arbitrary struct", func(t *testing.T) {
308 var w bytes.Buffer
309 type dummy struct {
310 Message string `json:"message,omitempty"`
311 }
312 data := dummy{Message: expected}
313 require.NoError(t, producer.Produce(&w, data))
314 assert.Equal(t, fmt.Sprintf(`{"message":%q}`, expected), w.String())
315 })
316
317 t.Run("can produce JSON from an arbitrary slice", func(t *testing.T) {
318 var w bytes.Buffer
319 data := []string{expected}
320 require.NoError(t, producer.Produce(&w, data))
321 assert.Equal(t, fmt.Sprintf(`[%q]`, expected), w.String())
322 })
323
324 t.Run("with CloseStream option", func(t *testing.T) {
325 t.Run("wants to close stream", func(t *testing.T) {
326 closingProducer := ByteStreamProducer(ClosesStream)
327 w := &closingWriter{}
328 data := bytes.NewBufferString(expected)
329
330 require.NoError(t, closingProducer.Produce(w, data))
331 assert.Equal(t, expected, w.String())
332 assert.EqualValues(t, 1, w.calledClose)
333 })
334
335 t.Run("don't want to close stream", func(t *testing.T) {
336 nonClosingProducer := ByteStreamProducer()
337 w := &closingWriter{}
338 data := bytes.NewBufferString(expected)
339
340 require.NoError(t, nonClosingProducer.Produce(w, data))
341 assert.Equal(t, expected, w.String())
342 assert.EqualValues(t, 0, w.calledClose)
343 })
344
345 t.Run("always close data reader whenever possible", func(t *testing.T) {
346 nonClosingProducer := ByteStreamProducer()
347 w := &closingWriter{}
348 data := &closingReader{b: bytes.NewBufferString(expected)}
349
350 require.NoError(t, nonClosingProducer.Produce(w, data))
351 assert.Equal(t, expected, w.String())
352 assert.EqualValuesf(t, 0, w.calledClose, "expected the input reader NOT to be closed")
353 assert.EqualValuesf(t, 1, data.calledClose, "expected the data reader to be closed")
354 })
355 })
356
357 t.Run("error cases", func(t *testing.T) {
358 t.Run("MarshalBinary error gets propagated", func(t *testing.T) {
359 var writer bytes.Buffer
360 data := new(binaryMarshalDummy)
361 require.Error(t, producer.Produce(&writer, data))
362 })
363
364 t.Run("nil data is never accepted", func(t *testing.T) {
365 var writer bytes.Buffer
366 require.Error(t, producer.Produce(&writer, nil))
367 })
368
369 t.Run("nil writer should also never be acccepted", func(t *testing.T) {
370 data := expected
371 require.Error(t, producer.Produce(nil, data))
372 })
373
374 t.Run("bool is an unsupported type", func(t *testing.T) {
375 var writer bytes.Buffer
376 data := true
377 require.Error(t, producer.Produce(&writer, data))
378 })
379
380 t.Run("WriteJSON error gets propagated", func(t *testing.T) {
381 var writer bytes.Buffer
382 type cannotMarshal struct {
383 X func() `json:"x"`
384 }
385 data := cannotMarshal{}
386 require.Error(t, producer.Produce(&writer, data))
387 })
388
389 })
390 }
391
392 type binaryUnmarshalDummy struct {
393 err error
394 str string
395 }
396
397 type binaryUnmarshalDummyZeroAlloc struct {
398 b []byte
399 }
400
401 func (b *binaryUnmarshalDummy) UnmarshalBinary(data []byte) error {
402 if b.err != nil {
403 return b.err
404 }
405
406 if len(data) == 0 {
407 return errors.New("no text given")
408 }
409
410 b.str = string(data)
411 return nil
412 }
413
414 func (b *binaryUnmarshalDummyZeroAlloc) UnmarshalBinary(data []byte) error {
415 if len(data) == 0 {
416 return errors.New("no text given")
417 }
418
419 b.b = data
420 return nil
421 }
422
423 type binaryMarshalDummy struct {
424 str string
425 }
426
427 func (b *binaryMarshalDummy) MarshalBinary() ([]byte, error) {
428 if len(b.str) == 0 {
429 return nil, errors.New("no text set")
430 }
431
432 return []byte(b.str), nil
433 }
434
435 type closingWriter struct {
436 calledClose int64
437 calledWrite int64
438 b bytes.Buffer
439 }
440
441 func (c *closingWriter) Close() error {
442 atomic.AddInt64(&c.calledClose, 1)
443 return nil
444 }
445
446 func (c *closingWriter) Write(p []byte) (n int, err error) {
447 atomic.AddInt64(&c.calledWrite, 1)
448 return c.b.Write(p)
449 }
450
451 func (c *closingWriter) String() string {
452 return c.b.String()
453 }
454
455 type closingReader struct {
456 calledClose int64
457 calledRead int64
458 b *bytes.Buffer
459 }
460
461 func (c *closingReader) Close() error {
462 atomic.AddInt64(&c.calledClose, 1)
463 return nil
464 }
465
466 func (c *closingReader) Read(p []byte) (n int, err error) {
467 atomic.AddInt64(&c.calledRead, 1)
468 return c.b.Read(p)
469 }
470
View as plain text