...

Source file src/github.com/go-openapi/runtime/bytestream_test.go

Documentation: github.com/go-openapi/runtime

     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