919 lines
23 KiB
Go
919 lines
23 KiB
Go
package capnp
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewMessage(t *testing.T) {
|
|
tests := []struct {
|
|
arena Arena
|
|
fails bool
|
|
}{
|
|
{arena: SingleSegment(nil)},
|
|
{arena: MultiSegment(nil)},
|
|
{arena: readOnlyArena{SingleSegment(make([]byte, 0, 7))}, fails: true},
|
|
{arena: readOnlyArena{SingleSegment(make([]byte, 0, 8))}},
|
|
{arena: MultiSegment(nil)},
|
|
{arena: MultiSegment([][]byte{make([]byte, 8)}), fails: true},
|
|
{arena: MultiSegment([][]byte{incrementingData(8)}), fails: true},
|
|
// This is somewhat arbitrary, but more than one segment = data.
|
|
// This restriction may be lifted if it's not useful.
|
|
{arena: MultiSegment([][]byte{make([]byte, 0, 16), make([]byte, 0)}), fails: true},
|
|
}
|
|
for _, test := range tests {
|
|
msg, seg, err := NewMessage(test.arena)
|
|
if err != nil {
|
|
if !test.fails {
|
|
t.Errorf("NewMessage(%v) failed unexpectedly: %v", test.arena, err)
|
|
}
|
|
continue
|
|
}
|
|
if test.fails {
|
|
t.Errorf("NewMessage(%v) succeeded; want error", test.arena)
|
|
continue
|
|
}
|
|
if n := msg.NumSegments(); n != 1 {
|
|
t.Errorf("NewMessage(%v).NumSegments() = %d; want 1", test.arena, n)
|
|
}
|
|
if seg.ID() != 0 {
|
|
t.Errorf("NewMessage(%v) segment.ID() = %d; want 0", test.arena, seg.ID())
|
|
}
|
|
if len(seg.Data()) != 8 {
|
|
t.Errorf("NewMessage(%v) segment.Data() = % 02x; want length 8", test.arena, seg.Data())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAlloc(t *testing.T) {
|
|
type allocTest struct {
|
|
name string
|
|
|
|
seg *Segment
|
|
size Size
|
|
|
|
allocID SegmentID
|
|
addr Address
|
|
}
|
|
var tests []allocTest
|
|
|
|
{
|
|
msg := &Message{Arena: SingleSegment(nil)}
|
|
seg, err := msg.Segment(0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests = append(tests, allocTest{
|
|
name: "empty alloc in empty segment",
|
|
seg: seg,
|
|
size: 0,
|
|
allocID: 0,
|
|
addr: 0,
|
|
})
|
|
}
|
|
{
|
|
msg := &Message{Arena: SingleSegment(nil)}
|
|
seg, err := msg.Segment(0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests = append(tests, allocTest{
|
|
name: "alloc in empty segment",
|
|
seg: seg,
|
|
size: 8,
|
|
allocID: 0,
|
|
addr: 0,
|
|
})
|
|
}
|
|
{
|
|
msg := &Message{Arena: MultiSegment([][]byte{
|
|
incrementingData(24)[:8],
|
|
incrementingData(24)[:8],
|
|
incrementingData(24)[:8],
|
|
})}
|
|
seg, err := msg.Segment(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests = append(tests, allocTest{
|
|
name: "prefers given segment",
|
|
seg: seg,
|
|
size: 16,
|
|
allocID: 1,
|
|
addr: 8,
|
|
})
|
|
}
|
|
{
|
|
msg := &Message{Arena: MultiSegment([][]byte{
|
|
incrementingData(24)[:8],
|
|
incrementingData(24),
|
|
})}
|
|
seg, err := msg.Segment(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests = append(tests, allocTest{
|
|
name: "given segment full with another available",
|
|
seg: seg,
|
|
size: 16,
|
|
allocID: 0,
|
|
addr: 8,
|
|
})
|
|
}
|
|
{
|
|
msg := &Message{Arena: MultiSegment([][]byte{
|
|
incrementingData(24),
|
|
incrementingData(24),
|
|
})}
|
|
seg, err := msg.Segment(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests = append(tests, allocTest{
|
|
name: "given segment full and no others available",
|
|
seg: seg,
|
|
size: 16,
|
|
allocID: 2,
|
|
addr: 0,
|
|
})
|
|
}
|
|
|
|
for i, test := range tests {
|
|
seg, addr, err := alloc(test.seg, test.size)
|
|
if err != nil {
|
|
t.Errorf("tests[%d] - %s: alloc(..., %d) error: %v", i, test.name, test.size, err)
|
|
continue
|
|
}
|
|
if seg.ID() != test.allocID {
|
|
t.Errorf("tests[%d] - %s: alloc(..., %d) returned segment %d; want segment %d", i, test.name, test.size, seg.ID(), test.allocID)
|
|
}
|
|
if addr != test.addr {
|
|
t.Errorf("tests[%d] - %s: alloc(..., %d) returned address %v; want address %v", i, test.name, test.size, addr, test.addr)
|
|
}
|
|
if !seg.regionInBounds(addr, test.size) {
|
|
t.Errorf("tests[%d] - %s: alloc(..., %d) returned address %v, which is not in bounds (len(seg.data) == %d)", i, test.name, test.size, addr, len(seg.Data()))
|
|
} else if data := seg.slice(addr, test.size); !isZeroFilled(data) {
|
|
t.Errorf("tests[%d] - %s: alloc(..., %d) region has data % 02x; want zero-filled", i, test.name, test.size, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSingleSegment(t *testing.T) {
|
|
// fresh arena
|
|
{
|
|
arena := SingleSegment(nil)
|
|
if n := arena.NumSegments(); n != 1 {
|
|
t.Errorf("SingleSegment(nil).NumSegments() = %d; want 1", n)
|
|
}
|
|
data, err := arena.Data(0)
|
|
if len(data) != 0 {
|
|
t.Errorf("SingleSegment(nil).Data(0) = %#v; want nil", data)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("SingleSegment(nil).Data(0) error: %v", err)
|
|
}
|
|
_, err = arena.Data(1)
|
|
if err == nil {
|
|
t.Error("SingleSegment(nil).Data(1) succeeded; want error")
|
|
}
|
|
}
|
|
|
|
// existing data
|
|
{
|
|
arena := SingleSegment(incrementingData(8))
|
|
if n := arena.NumSegments(); n != 1 {
|
|
t.Errorf("SingleSegment(incrementingData(8)).NumSegments() = %d; want 1", n)
|
|
}
|
|
data, err := arena.Data(0)
|
|
if want := incrementingData(8); !bytes.Equal(data, want) {
|
|
t.Errorf("SingleSegment(incrementingData(8)).Data(0) = %#v; want %#v", data, want)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("SingleSegment(incrementingData(8)).Data(0) error: %v", err)
|
|
}
|
|
_, err = arena.Data(1)
|
|
if err == nil {
|
|
t.Error("SingleSegment(incrementingData(8)).Data(1) succeeded; want error")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSingleSegmentAllocate(t *testing.T) {
|
|
tests := []arenaAllocTest{
|
|
{
|
|
name: "empty arena",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
return SingleSegment(nil), nil
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: []byte{},
|
|
},
|
|
{
|
|
name: "unloaded",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
return SingleSegment(buf[:16]), nil
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(16),
|
|
},
|
|
{
|
|
name: "loaded",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
buf = buf[:16]
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf},
|
|
}
|
|
return SingleSegment(buf), segs
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(16),
|
|
},
|
|
{
|
|
name: "loaded changes length",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(32)
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf[:24]},
|
|
}
|
|
return SingleSegment(buf[:16]), segs
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(24),
|
|
},
|
|
{
|
|
name: "message-filled segment",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf},
|
|
}
|
|
return SingleSegment(buf[:16]), segs
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(24),
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tests[i].run(t, i)
|
|
}
|
|
}
|
|
|
|
func TestMultiSegment(t *testing.T) {
|
|
// fresh arena
|
|
{
|
|
arena := MultiSegment(nil)
|
|
if n := arena.NumSegments(); n != 0 {
|
|
t.Errorf("MultiSegment(nil).NumSegments() = %d; want 1", n)
|
|
}
|
|
_, err := arena.Data(0)
|
|
if err == nil {
|
|
t.Error("MultiSegment(nil).Data(0) succeeded; want error")
|
|
}
|
|
}
|
|
|
|
// existing data
|
|
{
|
|
arena := MultiSegment([][]byte{incrementingData(8), incrementingData(24)})
|
|
if n := arena.NumSegments(); n != 2 {
|
|
t.Errorf("MultiSegment(...).NumSegments() = %d; want 2", n)
|
|
}
|
|
data, err := arena.Data(0)
|
|
if want := incrementingData(8); !bytes.Equal(data, want) {
|
|
t.Errorf("MultiSegment(...).Data(0) = %#v; want %#v", data, want)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("MultiSegment(...).Data(0) error: %v", err)
|
|
}
|
|
data, err = arena.Data(1)
|
|
if want := incrementingData(24); !bytes.Equal(data, want) {
|
|
t.Errorf("MultiSegment(...).Data(1) = %#v; want %#v", data, want)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("MultiSegment(...).Data(1) error: %v", err)
|
|
}
|
|
_, err = arena.Data(2)
|
|
if err == nil {
|
|
t.Error("MultiSegment(...).Data(2) succeeded; want error")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultiSegmentAllocate(t *testing.T) {
|
|
tests := []arenaAllocTest{
|
|
{
|
|
name: "empty arena",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
return MultiSegment(nil), nil
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: []byte{},
|
|
},
|
|
{
|
|
name: "space in unloaded segment",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
return MultiSegment([][]byte{buf[:16]}), nil
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(16),
|
|
},
|
|
{
|
|
name: "space in loaded segment",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
buf = buf[:16]
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf},
|
|
}
|
|
return MultiSegment([][]byte{buf}), segs
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(16),
|
|
},
|
|
{
|
|
name: "space in loaded segment changes length",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(32)
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf[:24]},
|
|
}
|
|
return MultiSegment([][]byte{buf[:16]}), segs
|
|
},
|
|
size: 8,
|
|
id: 0,
|
|
data: incrementingData(24),
|
|
},
|
|
{
|
|
name: "message-filled segment",
|
|
init: func() (Arena, map[SegmentID]*Segment) {
|
|
buf := incrementingData(24)
|
|
segs := map[SegmentID]*Segment{
|
|
0: {id: 0, data: buf},
|
|
}
|
|
return MultiSegment([][]byte{buf[:16]}), segs
|
|
},
|
|
size: 8,
|
|
id: 1,
|
|
data: []byte{},
|
|
},
|
|
}
|
|
|
|
for i := range tests {
|
|
tests[i].run(t, i)
|
|
}
|
|
}
|
|
|
|
type serializeTest struct {
|
|
name string
|
|
segs [][]byte
|
|
out []byte
|
|
encodeFails bool
|
|
decodeFails bool
|
|
decodeError error
|
|
}
|
|
|
|
func (st *serializeTest) arena() Arena {
|
|
bb := make([][]byte, len(st.segs))
|
|
for i := range bb {
|
|
bb[i] = make([]byte, len(st.segs[i]))
|
|
copy(bb[i], st.segs[i])
|
|
}
|
|
return MultiSegment(bb)
|
|
}
|
|
|
|
func (st *serializeTest) copyOut() []byte {
|
|
out := make([]byte, len(st.out))
|
|
copy(out, st.out)
|
|
return out
|
|
}
|
|
|
|
var serializeTests = []serializeTest{
|
|
{
|
|
name: "empty message",
|
|
segs: [][]byte{},
|
|
encodeFails: true,
|
|
},
|
|
{
|
|
name: "empty stream",
|
|
out: []byte{},
|
|
decodeFails: true,
|
|
decodeError: io.EOF,
|
|
},
|
|
{
|
|
name: "incomplete segment count",
|
|
out: []byte{0x01},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "incomplete segment size",
|
|
out: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00,
|
|
},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "empty single segment",
|
|
segs: [][]byte{
|
|
{},
|
|
},
|
|
out: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "missing segment data",
|
|
out: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "missing segment size",
|
|
out: []byte{
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "missing segment size padding",
|
|
out: []byte{
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "single segment",
|
|
segs: [][]byte{
|
|
incrementingData(8),
|
|
},
|
|
out: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
},
|
|
},
|
|
{
|
|
name: "two segments",
|
|
segs: [][]byte{
|
|
incrementingData(8),
|
|
incrementingData(8),
|
|
},
|
|
out: []byte{
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
},
|
|
},
|
|
{
|
|
name: "two segments, missing size padding",
|
|
out: []byte{
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
},
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "HTTP traffic should not panic on GOARCH=386",
|
|
out: []byte("GET / HTTP/1.1\r\n\r\n"),
|
|
decodeFails: true,
|
|
},
|
|
{
|
|
name: "max segment should not panic",
|
|
out: bytes.Repeat([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 16),
|
|
decodeFails: true,
|
|
},
|
|
}
|
|
|
|
func TestMarshal(t *testing.T) {
|
|
for i, test := range serializeTests {
|
|
if test.decodeFails {
|
|
continue
|
|
}
|
|
msg := &Message{Arena: test.arena()}
|
|
out, err := msg.Marshal()
|
|
if err != nil {
|
|
if !test.encodeFails {
|
|
t.Errorf("serializeTests[%d] %s: Marshal error: %v", i, test.name, err)
|
|
}
|
|
continue
|
|
}
|
|
if test.encodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Marshal success; want error", i, test.name)
|
|
continue
|
|
}
|
|
if !bytes.Equal(out, test.out) {
|
|
t.Errorf("serializeTests[%d] - %s: Marshal = % 02x; want % 02x", i, test.name, out, test.out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnmarshal(t *testing.T) {
|
|
for i, test := range serializeTests {
|
|
if test.encodeFails {
|
|
continue
|
|
}
|
|
msg, err := Unmarshal(test.copyOut())
|
|
if err != nil {
|
|
if !test.decodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal error: %v", i, test.name, err)
|
|
}
|
|
if test.decodeError != nil && err != test.decodeError {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal error: %v; want %v", i, test.name, err, test.decodeError)
|
|
}
|
|
continue
|
|
}
|
|
if test.decodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal success; want error", i, test.name)
|
|
continue
|
|
}
|
|
if msg.NumSegments() != int64(len(test.segs)) {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal NumSegments() = %d; want %d", i, test.name, msg.NumSegments(), len(test.segs))
|
|
continue
|
|
}
|
|
for j := range test.segs {
|
|
seg, err := msg.Segment(SegmentID(j))
|
|
if err != nil {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal Segment(%d) error: %v", i, test.name, j, err)
|
|
continue
|
|
}
|
|
if !bytes.Equal(seg.Data(), test.segs[j]) {
|
|
t.Errorf("serializeTests[%d] - %s: Unmarshal Segment(%d) = % 02x; want % 02x", i, test.name, j, seg.Data(), test.segs[j])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEncoder(t *testing.T) {
|
|
for i, test := range serializeTests {
|
|
if test.decodeFails {
|
|
continue
|
|
}
|
|
msg := &Message{Arena: test.arena()}
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
err := enc.Encode(msg)
|
|
out := buf.Bytes()
|
|
if err != nil {
|
|
if !test.encodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Encode error: %v", i, test.name, err)
|
|
}
|
|
continue
|
|
}
|
|
if test.encodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Encode success; want error", i, test.name)
|
|
continue
|
|
}
|
|
if !bytes.Equal(out, test.out) {
|
|
t.Errorf("serializeTests[%d] - %s: Encode = % 02x; want % 02x", i, test.name, out, test.out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecoder(t *testing.T) {
|
|
for i, test := range serializeTests {
|
|
if test.encodeFails {
|
|
continue
|
|
}
|
|
msg, err := NewDecoder(bytes.NewReader(test.out)).Decode()
|
|
if err != nil {
|
|
if !test.decodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Decode error: %v", i, test.name, err)
|
|
}
|
|
if test.decodeError != nil && err != test.decodeError {
|
|
t.Errorf("serializeTests[%d] - %s: Decode error: %v; want %v", i, test.name, err, test.decodeError)
|
|
}
|
|
continue
|
|
}
|
|
if test.decodeFails {
|
|
t.Errorf("serializeTests[%d] - %s: Decode success; want error", i, test.name)
|
|
continue
|
|
}
|
|
if msg.NumSegments() != int64(len(test.segs)) {
|
|
t.Errorf("serializeTests[%d] - %s: Decode NumSegments() = %d; want %d", i, test.name, msg.NumSegments(), len(test.segs))
|
|
continue
|
|
}
|
|
for j := range test.segs {
|
|
seg, err := msg.Segment(SegmentID(j))
|
|
if err != nil {
|
|
t.Errorf("serializeTests[%d] - %s: Decode Segment(%d) error: %v", i, test.name, j, err)
|
|
continue
|
|
}
|
|
if !bytes.Equal(seg.Data(), test.segs[j]) {
|
|
t.Errorf("serializeTests[%d] - %s: Decode Segment(%d) = % 02x; want % 02x", i, test.name, j, seg.Data(), test.segs[j])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecoder_MaxMessageSize(t *testing.T) {
|
|
t.Parallel()
|
|
zeroWord := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
tests := []struct {
|
|
name string
|
|
maxSize uint64
|
|
r io.Reader
|
|
ok bool
|
|
}{
|
|
{
|
|
name: "header too big",
|
|
maxSize: 15,
|
|
r: bytes.NewReader([]byte{
|
|
0x02, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
}),
|
|
},
|
|
{
|
|
name: "header at limit",
|
|
maxSize: 16,
|
|
r: bytes.NewReader([]byte{
|
|
0x02, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
}),
|
|
ok: true,
|
|
},
|
|
{
|
|
name: "body too large",
|
|
maxSize: 64,
|
|
r: io.MultiReader(
|
|
bytes.NewReader([]byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x09, 0x00, 0x00, 0x00,
|
|
}),
|
|
bytes.NewReader(bytes.Repeat(zeroWord, 9)),
|
|
),
|
|
},
|
|
{
|
|
name: "body plus header too large",
|
|
maxSize: 64,
|
|
r: io.MultiReader(
|
|
bytes.NewReader([]byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x08, 0x00, 0x00, 0x00,
|
|
}),
|
|
bytes.NewReader(bytes.Repeat(zeroWord, 8)),
|
|
),
|
|
},
|
|
{
|
|
name: "body plus header at limit",
|
|
maxSize: 72,
|
|
r: io.MultiReader(
|
|
bytes.NewReader([]byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x08, 0x00, 0x00, 0x00,
|
|
}),
|
|
bytes.NewReader(bytes.Repeat(zeroWord, 8)),
|
|
),
|
|
ok: true,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
d := NewDecoder(test.r)
|
|
d.MaxMessageSize = test.maxSize
|
|
_, err := d.Decode()
|
|
switch {
|
|
case err != nil && test.ok:
|
|
t.Errorf("%s test: Decode error: %v", test.name, err)
|
|
case err == nil && !test.ok:
|
|
t.Errorf("%s test: Decode success; want error", test.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestStreamHeaderPadding is a regression test for
|
|
// stream header padding.
|
|
//
|
|
// Encoder reuses a buffer for stream header marshalling,
|
|
// this test ensures that the padding is explicitly
|
|
// zeroed. This was not done in previous versions and
|
|
// resulted in the padding being garbage.
|
|
func TestStreamHeaderPadding(t *testing.T) {
|
|
msg := &Message{
|
|
Arena: MultiSegment([][]byte{
|
|
incrementingData(8),
|
|
incrementingData(8),
|
|
incrementingData(8),
|
|
}),
|
|
}
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
err := enc.Encode(msg)
|
|
buf.Reset()
|
|
if err != nil {
|
|
t.Fatalf("Encode error: %v", err)
|
|
}
|
|
msg = &Message{
|
|
Arena: MultiSegment([][]byte{
|
|
incrementingData(8),
|
|
incrementingData(8),
|
|
}),
|
|
}
|
|
err = enc.Encode(msg)
|
|
out := buf.Bytes()
|
|
if err != nil {
|
|
t.Fatalf("Encode error: %v", err)
|
|
}
|
|
want := []byte{
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x02, 0x03,
|
|
0x04, 0x05, 0x06, 0x07,
|
|
0x00, 0x01, 0x02, 0x03,
|
|
0x04, 0x05, 0x06, 0x07,
|
|
}
|
|
if !bytes.Equal(out, want) {
|
|
t.Errorf("Encode = % 02x; want % 02x", out, want)
|
|
}
|
|
}
|
|
|
|
func TestFirstSegmentMessage_SingleSegment(t *testing.T) {
|
|
msg, seg, err := NewMessage(SingleSegment(nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if msg.NumSegments() != 1 {
|
|
t.Errorf("msg.NumSegments() = %d; want 1", msg.NumSegments())
|
|
}
|
|
if seg.Message() != msg {
|
|
t.Errorf("seg.Message() = %p; want %p", seg.Message(), msg)
|
|
}
|
|
if seg.ID() != 0 {
|
|
t.Errorf("seg.ID() = %d; want 0", seg.ID())
|
|
}
|
|
if seg0, err := msg.Segment(0); err != nil {
|
|
t.Errorf("msg.Segment(0): %v", err)
|
|
} else if seg0 != seg {
|
|
t.Errorf("msg.Segment(0) = %p; want %p", seg0, seg)
|
|
}
|
|
}
|
|
|
|
func TestFirstSegmentMessage_MultiSegment(t *testing.T) {
|
|
msg, seg, err := NewMessage(MultiSegment(nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if msg.NumSegments() != 1 {
|
|
t.Errorf("msg.NumSegments() = %d; want 1", msg.NumSegments())
|
|
}
|
|
if seg.Message() != msg {
|
|
t.Errorf("seg.Message() = %p; want %p", seg.Message(), msg)
|
|
}
|
|
if seg.ID() != 0 {
|
|
t.Errorf("seg.ID() = %d; want 0", seg.ID())
|
|
}
|
|
if seg0, err := msg.Segment(0); err != nil {
|
|
t.Errorf("msg.Segment(0): %v", err)
|
|
} else if seg0 != seg {
|
|
t.Errorf("msg.Segment(0) = %p; want %p", seg0, seg)
|
|
}
|
|
}
|
|
|
|
func TestNextAlloc(t *testing.T) {
|
|
const max32 = 1<<31 - 8
|
|
const max64 = 1<<63 - 8
|
|
tests := []struct {
|
|
name string
|
|
curr int64
|
|
max int64
|
|
req Size
|
|
ok bool
|
|
}{
|
|
{name: "zero", curr: 0, max: max64, req: 0, ok: true},
|
|
{name: "first word", curr: 0, max: max64, req: 8, ok: true},
|
|
{name: "first word, unaligned curr", curr: 13, max: max64, req: 8, ok: true},
|
|
{name: "second word", curr: 8, max: max64, req: 8, ok: true},
|
|
{name: "one byte pads to word", curr: 8, max: max64, req: 1, ok: true},
|
|
{name: "max size", curr: 0, max: max64, req: 0xfffffff8, ok: true},
|
|
{name: "max size + 1", curr: 0, max: max64, req: 0xfffffff9, ok: false},
|
|
{name: "max req", curr: 0, max: max64, req: 0xffffffff, ok: false},
|
|
{name: "max curr, request 0", curr: max64, max: max64, req: 0, ok: true},
|
|
{name: "max curr, request 1", curr: max64, max: max64, req: 1, ok: false},
|
|
{name: "medium curr, request 2 words", curr: 4 << 20, max: max64, req: 16, ok: true},
|
|
{name: "large curr, request word", curr: 1 << 34, max: max64, req: 8, ok: true},
|
|
{name: "large unaligned curr, request word", curr: 1<<34 + 13, max: max64, req: 8, ok: true},
|
|
{name: "2<<31-8 curr, request 0", curr: 2<<31 - 8, max: max64, req: 0, ok: true},
|
|
{name: "2<<31-8 curr, request 1", curr: 2<<31 - 8, max: max64, req: 1, ok: true},
|
|
{name: "2<<31-8 curr, 32-bit max, request 0", curr: 2<<31 - 8, max: max32, req: 0, ok: true},
|
|
{name: "2<<31-8 curr, 32-bit max, request 1", curr: 2<<31 - 8, max: max32, req: 1, ok: false},
|
|
}
|
|
for _, test := range tests {
|
|
if test.max%8 != 0 {
|
|
t.Errorf("%s: max must be word-aligned. Skipped.", test.name)
|
|
continue
|
|
}
|
|
got, err := nextAlloc(test.curr, test.max, test.req)
|
|
if err != nil {
|
|
if test.ok {
|
|
t.Errorf("%s: nextAlloc(%d, %d, %d) = _, %v; want >=%d, <nil>", test.name, test.curr, test.max, test.req, err, test.req)
|
|
}
|
|
continue
|
|
}
|
|
if !test.ok {
|
|
t.Errorf("%s: nextAlloc(%d, %d, %d) = %d, <nil>; want _, <error>", test.name, test.curr, test.max, test.req, got)
|
|
continue
|
|
}
|
|
max := test.max - test.curr
|
|
if max < 0 {
|
|
max = 0
|
|
}
|
|
if int64(got) < int64(test.req) || int64(got) > max {
|
|
t.Errorf("%s: nextAlloc(%d, %d, %d) = %d, <nil>; want in range [%d, %d]", test.name, test.curr, test.max, test.req, got, test.req, max)
|
|
}
|
|
if got%8 != 0 {
|
|
t.Errorf("%s: nextAlloc(%d, %d, %d) = %d, <nil>; want divisible by 8 (word size)", test.name, test.curr, test.max, test.req, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
type arenaAllocTest struct {
|
|
name string
|
|
|
|
// Arrange
|
|
init func() (Arena, map[SegmentID]*Segment)
|
|
size Size
|
|
|
|
// Assert
|
|
id SegmentID
|
|
data []byte
|
|
}
|
|
|
|
func (test *arenaAllocTest) run(t *testing.T, i int) {
|
|
arena, segs := test.init()
|
|
id, data, err := arena.Allocate(test.size, segs)
|
|
|
|
if err != nil {
|
|
t.Errorf("tests[%d] - %s: Allocate error: %v", i, test.name, err)
|
|
return
|
|
}
|
|
if id != test.id {
|
|
t.Errorf("tests[%d] - %s: Allocate id = %d; want %d", i, test.name, id, test.id)
|
|
}
|
|
if !bytes.Equal(data, test.data) {
|
|
t.Errorf("tests[%d] - %s: Allocate data = % 02x; want % 02x", i, test.name, data, test.data)
|
|
}
|
|
if Size(cap(data)-len(data)) < test.size {
|
|
t.Errorf("tests[%d] - %s: Allocate len(data) = %d, cap(data) = %d; cap(data) should be at least %d", i, test.name, len(data), cap(data), Size(len(data))+test.size)
|
|
}
|
|
}
|
|
|
|
func incrementingData(n int) []byte {
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = byte(i % 256)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func isZeroFilled(b []byte) bool {
|
|
for _, bb := range b {
|
|
if bb != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
type readOnlyArena struct {
|
|
Arena
|
|
}
|
|
|
|
func (ro readOnlyArena) String() string {
|
|
return fmt.Sprintf("readOnlyArena{%v}", ro.Arena)
|
|
}
|
|
|
|
func (readOnlyArena) Allocate(sz Size, segs map[SegmentID]*Segment) (SegmentID, []byte, error) {
|
|
return 0, nil, errReadOnlyArena
|
|
}
|
|
|
|
var errReadOnlyArena = errors.New("Allocate called on read-only arena")
|