forked from quic-go/quic-go
315 lines
8.9 KiB
Go
315 lines
8.9 KiB
Go
package quicvarint
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"math/rand/v2"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLimits(t *testing.T) {
|
|
require.Equal(t, 0, Min)
|
|
require.Equal(t, uint64(1<<62-1), uint64(Max))
|
|
}
|
|
|
|
func TestRead(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expected uint64
|
|
}{
|
|
{"1 byte", []byte{0b00011001}, 25},
|
|
{"2 byte", []byte{0b01111011, 0xbd}, 15293},
|
|
{"4 byte", []byte{0b10011101, 0x7f, 0x3e, 0x7d}, 494878333},
|
|
{"8 byte", []byte{0b11000010, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}, 151288809941952652},
|
|
{"too long", []byte{0b01000000, 0x25}, 37},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
b := bytes.NewReader(tt.input)
|
|
val, err := Read(b)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, val)
|
|
require.Zero(t, b.Len())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedValue uint64
|
|
expectedLen int
|
|
}{
|
|
{"1 byte", []byte{0b00011001}, 25, 1},
|
|
{"2 byte", []byte{0b01111011, 0xbd}, 15293, 2},
|
|
{"4 byte", []byte{0b10011101, 0x7f, 0x3e, 0x7d}, 494878333, 4},
|
|
{"8 byte", []byte{0b11000010, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}, 151288809941952652, 8},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
value, l, err := Parse(tt.input)
|
|
require.Equal(t, tt.expectedValue, value)
|
|
require.Equal(t, tt.expectedLen, l)
|
|
require.Nil(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsingFailures(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "empty slice",
|
|
input: []byte{},
|
|
expectedErr: io.EOF,
|
|
},
|
|
{
|
|
name: "2-byte encoding: not enough bytes",
|
|
input: []byte{0b01000001},
|
|
expectedErr: io.ErrUnexpectedEOF,
|
|
},
|
|
{
|
|
name: "4-byte encoding: not enough bytes",
|
|
input: []byte{0b10000000, 0x0, 0x0},
|
|
expectedErr: io.ErrUnexpectedEOF,
|
|
},
|
|
{
|
|
name: "8-byte encoding: not enough bytes",
|
|
input: []byte{0b11000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
|
expectedErr: io.ErrUnexpectedEOF,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
value, l, err := Parse(tt.input)
|
|
require.Equal(t, uint64(0), value)
|
|
require.Equal(t, 0, l)
|
|
require.Equal(t, tt.expectedErr, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVarintEncoding(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
expected []byte
|
|
}{
|
|
{"1 byte number", 37, []byte{0x25}},
|
|
{"maximum 1 byte number", maxVarInt1, []byte{0b00111111}},
|
|
{"minimum 2 byte number", maxVarInt1 + 1, []byte{0x40, maxVarInt1 + 1}},
|
|
{"2 byte number", 15293, []byte{0b01000000 ^ 0x3b, 0xbd}},
|
|
{"maximum 2 byte number", maxVarInt2, []byte{0b01111111, 0xff}},
|
|
{"minimum 4 byte number", maxVarInt2 + 1, []byte{0b10000000, 0, 0x40, 0}},
|
|
{"4 byte number", 494878333, []byte{0b10000000 ^ 0x1d, 0x7f, 0x3e, 0x7d}},
|
|
{"maximum 4 byte number", maxVarInt4, []byte{0b10111111, 0xff, 0xff, 0xff}},
|
|
{"minimum 8 byte number", maxVarInt4 + 1, []byte{0b11000000, 0, 0, 0, 0x40, 0, 0, 0}},
|
|
{"8 byte number", 151288809941952652, []byte{0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}},
|
|
{"maximum 8 byte number", maxVarInt8, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.expected, Append(nil, tt.value))
|
|
})
|
|
}
|
|
|
|
t.Run("panics when given a too large number (> 62 bit)", func(t *testing.T) {
|
|
require.Panics(t, func() { Append(nil, maxVarInt8+1) })
|
|
})
|
|
}
|
|
|
|
func TestAppendWithLen(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
length int
|
|
expected []byte
|
|
}{
|
|
{"1-byte number in minimal encoding", 37, 1, []byte{0x25}},
|
|
{"1-byte number in 2 bytes", 37, 2, []byte{0b01000000, 0x25}},
|
|
{"1-byte number in 4 bytes", 37, 4, []byte{0b10000000, 0, 0, 0x25}},
|
|
{"1-byte number in 8 bytes", 37, 8, []byte{0b11000000, 0, 0, 0, 0, 0, 0, 0x25}},
|
|
{"2-byte number in 4 bytes", 15293, 4, []byte{0b10000000, 0, 0x3b, 0xbd}},
|
|
{"4-byte number in 8 bytes", 494878333, 8, []byte{0b11000000, 0, 0, 0, 0x1d, 0x7f, 0x3e, 0x7d}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
b := AppendWithLen(nil, tt.value, tt.length)
|
|
require.Equal(t, tt.expected, b)
|
|
|
|
if tt.length > 1 {
|
|
v, n, err := Parse(b)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.length, n)
|
|
require.Equal(t, tt.value, v)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAppendWithLenFailures(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
length int
|
|
}{
|
|
{"invalid length", 25, 3},
|
|
{"too short for 2 bytes", maxVarInt1 + 1, 1},
|
|
{"too short for 4 bytes", maxVarInt2 + 1, 2},
|
|
{"too short for 8 bytes", maxVarInt4 + 1, 4},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Panics(t, func() {
|
|
AppendWithLen(nil, tt.value, tt.length)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLen(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input uint64
|
|
expected int
|
|
}{
|
|
{"zero", 0, 1},
|
|
{"max 1 byte", maxVarInt1, 1},
|
|
{"min 2 bytes", maxVarInt1 + 1, 2},
|
|
{"max 2 bytes", maxVarInt2, 2},
|
|
{"min 4 bytes", maxVarInt2 + 1, 4},
|
|
{"max 4 bytes", maxVarInt4, 4},
|
|
{"min 8 bytes", maxVarInt4 + 1, 8},
|
|
{"max 8 bytes", maxVarInt8, 8},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.expected, Len(tt.input))
|
|
})
|
|
}
|
|
|
|
t.Run("panics on too large number", func(t *testing.T) {
|
|
require.Panics(t, func() { Len(maxVarInt8 + 1) })
|
|
})
|
|
}
|
|
|
|
type benchmarkValue struct {
|
|
b []byte
|
|
v uint64
|
|
}
|
|
|
|
func randomValues(num int, maxValue uint64) []benchmarkValue {
|
|
r := rand.New(rand.NewPCG(13, 37))
|
|
|
|
bv := make([]benchmarkValue, num)
|
|
for i := range num {
|
|
v := r.Uint64() % maxValue
|
|
bv[i].v = v
|
|
bv[i].b = Append([]byte{}, v)
|
|
}
|
|
return bv
|
|
}
|
|
|
|
func BenchmarkRead(b *testing.B) {
|
|
b.Run("1-byte", func(b *testing.B) { benchmarkRead(b, randomValues(min(b.N, 1024), maxVarInt1)) })
|
|
b.Run("2-byte", func(b *testing.B) { benchmarkRead(b, randomValues(min(b.N, 1024), maxVarInt2)) })
|
|
b.Run("4-byte", func(b *testing.B) { benchmarkRead(b, randomValues(min(b.N, 1024), maxVarInt4)) })
|
|
b.Run("8-byte", func(b *testing.B) { benchmarkRead(b, randomValues(min(b.N, 1024), maxVarInt8)) })
|
|
}
|
|
|
|
func benchmarkRead(b *testing.B, inputs []benchmarkValue) {
|
|
r := bytes.NewReader([]byte{})
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
index := i % len(inputs)
|
|
r.Reset(inputs[index].b)
|
|
val, err := Read(r)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if val != inputs[index].v {
|
|
b.Fatalf("expected %d, got %d", inputs[index].v, val)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkParse(b *testing.B) {
|
|
b.Run("1-byte", func(b *testing.B) { benchmarkParse(b, randomValues(min(b.N, 1024), maxVarInt1)) })
|
|
b.Run("2-byte", func(b *testing.B) { benchmarkParse(b, randomValues(min(b.N, 1024), maxVarInt2)) })
|
|
b.Run("4-byte", func(b *testing.B) { benchmarkParse(b, randomValues(min(b.N, 1024), maxVarInt4)) })
|
|
b.Run("8-byte", func(b *testing.B) { benchmarkParse(b, randomValues(min(b.N, 1024), maxVarInt8)) })
|
|
}
|
|
|
|
func benchmarkParse(b *testing.B, inputs []benchmarkValue) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
index := i % 1024
|
|
val, n, err := Parse(inputs[index].b)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if n != len(inputs[index].b) {
|
|
b.Fatalf("expected to consume %d bytes, consumed %d", len(inputs[i].b), n)
|
|
}
|
|
if val != inputs[index].v {
|
|
b.Fatalf("expected %d, got %d", inputs[index].v, val)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppend(b *testing.B) {
|
|
b.Run("1-byte", func(b *testing.B) { benchmarkAppend(b, randomValues(min(b.N, 1024), maxVarInt1)) })
|
|
b.Run("2-byte", func(b *testing.B) { benchmarkAppend(b, randomValues(min(b.N, 1024), maxVarInt2)) })
|
|
b.Run("4-byte", func(b *testing.B) { benchmarkAppend(b, randomValues(min(b.N, 1024), maxVarInt4)) })
|
|
b.Run("8-byte", func(b *testing.B) { benchmarkAppend(b, randomValues(min(b.N, 1024), maxVarInt8)) })
|
|
}
|
|
|
|
func benchmarkAppend(b *testing.B, inputs []benchmarkValue) {
|
|
buf := make([]byte, 8)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
buf = buf[:0]
|
|
index := i % 1024
|
|
buf = Append(buf, inputs[index].v)
|
|
|
|
if !bytes.Equal(buf, inputs[index].b) {
|
|
b.Fatalf("expected to write %v, wrote %v", inputs[index].b, buf)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendWithLen(b *testing.B) {
|
|
b.Run("1-byte", func(b *testing.B) { benchmarkAppendWithLen(b, randomValues(min(b.N, 1024), maxVarInt1)) })
|
|
b.Run("2-byte", func(b *testing.B) { benchmarkAppendWithLen(b, randomValues(min(b.N, 1024), maxVarInt2)) })
|
|
b.Run("4-byte", func(b *testing.B) { benchmarkAppendWithLen(b, randomValues(min(b.N, 1024), maxVarInt4)) })
|
|
b.Run("8-byte", func(b *testing.B) { benchmarkAppendWithLen(b, randomValues(min(b.N, 1024), maxVarInt8)) })
|
|
}
|
|
|
|
func benchmarkAppendWithLen(b *testing.B, inputs []benchmarkValue) {
|
|
buf := make([]byte, 8)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
buf = buf[:0]
|
|
index := i % 1024
|
|
buf = AppendWithLen(buf, inputs[index].v, len(inputs[index].b))
|
|
|
|
if !bytes.Equal(buf, inputs[index].b) {
|
|
b.Fatalf("expected to write %v, wrote %v", inputs[index].b, buf)
|
|
}
|
|
}
|
|
}
|