forked from quic-go/quic-go
* Tolerate empty reads in `quicvarint.Read` Currently, `quicvarint.Read` misinterprets an empty read (`n==0`) as a single zero byte. While empty reads are "discouraged" in the `io.Reader` interface, they do sometimes happen, especially when using `io.Pipe()`. This change tolerates empty reads, adds a corresponding unit test, and also includes some additional test coverage related to empty capsules * minor test refactor --------- Co-authored-by: Marten Seemann <martenseemann@gmail.com>
117 lines
2.4 KiB
Go
117 lines
2.4 KiB
Go
package quicvarint
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type nopReader struct{}
|
|
|
|
func (r *nopReader) Read(_ []byte) (int, error) {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
|
|
var _ io.Reader = &nopReader{}
|
|
|
|
type nopWriter struct{}
|
|
|
|
func (r *nopWriter) Write(_ []byte) (int, error) {
|
|
return 0, io.ErrShortBuffer
|
|
}
|
|
|
|
// eofReader is a reader that returns data and the io.EOF at the same time in the last Read call
|
|
type eofReader struct {
|
|
Data []byte
|
|
pos int
|
|
}
|
|
|
|
func (r *eofReader) Read(b []byte) (int, error) {
|
|
n := copy(b, r.Data[r.pos:])
|
|
r.pos += n
|
|
if r.pos >= len(r.Data) {
|
|
return n, io.EOF
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
var _ io.Writer = &nopWriter{}
|
|
|
|
func TestReaderPassesThroughUnchanged(t *testing.T) {
|
|
b := bytes.NewReader([]byte{0})
|
|
r := NewReader(b)
|
|
require.Equal(t, b, r)
|
|
}
|
|
|
|
func TestReaderWrapsIOReader(t *testing.T) {
|
|
n := &nopReader{}
|
|
r := NewReader(n)
|
|
require.NotEqual(t, n, r)
|
|
}
|
|
|
|
func TestReaderFailure(t *testing.T) {
|
|
r := NewReader(&nopReader{})
|
|
val, err := r.ReadByte()
|
|
require.Equal(t, io.ErrUnexpectedEOF, err)
|
|
require.Equal(t, byte(0), val)
|
|
}
|
|
|
|
func TestReaderHandlesEOF(t *testing.T) {
|
|
// test that the eofReader behaves as we expect
|
|
r := &eofReader{Data: []byte("foobar")}
|
|
b := make([]byte, 3)
|
|
n, err := r.Read(b)
|
|
require.Equal(t, 3, n)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "foo", string(b))
|
|
n, err = r.Read(b)
|
|
require.Equal(t, 3, n)
|
|
require.Equal(t, io.EOF, err)
|
|
require.Equal(t, "bar", string(b))
|
|
n, err = r.Read(b)
|
|
require.Equal(t, io.EOF, err)
|
|
require.Zero(t, n)
|
|
|
|
// now test using it to read varints
|
|
reader := NewReader(&eofReader{Data: Append(nil, 1337)})
|
|
n2, err := Read(reader)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 1337, n2)
|
|
}
|
|
|
|
// Regression test: empty reads were being converted to successful
|
|
// reads of a zero value.
|
|
func TestReaderHandlesEmptyRead(t *testing.T) {
|
|
r, w := io.Pipe()
|
|
|
|
go func() {
|
|
// io.Pipe turns empty writes into empty reads.
|
|
w.Write(nil)
|
|
w.Close()
|
|
}()
|
|
|
|
br := NewReader(r)
|
|
_, err := Read(br)
|
|
require.ErrorIs(t, err, io.EOF)
|
|
}
|
|
|
|
func TestWriterPassesThroughUnchanged(t *testing.T) {
|
|
b := &bytes.Buffer{}
|
|
w := NewWriter(b)
|
|
require.Equal(t, b, w)
|
|
}
|
|
|
|
func TestWriterWrapsIOWriter(t *testing.T) {
|
|
n := &nopWriter{}
|
|
w := NewWriter(n)
|
|
require.NotEqual(t, n, w)
|
|
}
|
|
|
|
func TestWriterFailure(t *testing.T) {
|
|
w := NewWriter(&nopWriter{})
|
|
err := w.WriteByte(0)
|
|
require.Equal(t, io.ErrShortBuffer, err)
|
|
}
|