grumble/pkg/freezer/freezer_test.go
2012-02-06 22:17:42 +01:00

280 lines
5.6 KiB
Go

// Copyright (c) 2011 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package freezer
import (
"bytes"
"code.google.com/p/goprotobuf/proto"
"encoding/binary"
"hash/crc32"
"io"
"math"
"os"
"testing"
)
var testValues []interface{} = []interface{}{
&ConfigKeyValuePair{Key: proto.String("Foo")},
&BanList{Bans: []*Ban{&Ban{Mask: proto.Uint32(32)}}},
&User{Id: proto.Uint32(0), Name: proto.String("SuperUser")},
&UserRemove{Id: proto.Uint32(0)},
&Channel{Id: proto.Uint32(0), Name: proto.String("RootChannel")},
&ChannelRemove{Id: proto.Uint32(0)},
}
// Generate a byet slice representing an entry in a Tx record
func genTxValue(kind uint16, val []byte) (chunk []byte, crc32sum uint32, err error) {
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, kind)
if err != nil {
return nil, 0, err
}
err = binary.Write(buf, binary.LittleEndian, uint16(len(val)))
if err != nil {
return nil, 0, err
}
_, err = buf.Write(val)
if err != nil {
return nil, 0, err
}
summer := crc32.NewIEEE()
_, err = summer.Write(val)
if err != nil {
return nil, 0, err
}
return buf.Bytes(), summer.Sum32(), nil
}
// Generate the header of a Tx record
func genTestCaseHeader(chunk []byte, numops uint32, crc32sum uint32) (r io.Reader, err error) {
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, uint32(4+4+len(chunk)))
if err != nil {
return nil, err
}
err = binary.Write(buf, binary.LittleEndian, numops)
if err != nil {
return nil, err
}
err = binary.Write(buf, binary.LittleEndian, crc32sum)
if err != nil {
return nil, err
}
_, err = buf.Write(chunk)
if err != nil {
return nil, err
}
return buf, nil
}
// Test that the Walker and the Writer agree on the
// protocol.
func TestCreation(t *testing.T) {
l, err := NewLogFile("creation.log")
if err != nil {
t.Error(err)
return
}
l.Close()
os.Remove("creation.log")
}
func TestLogging(t *testing.T) {
l, err := NewLogFile("logging.log")
if err != nil {
t.Error(err)
return
}
defer l.Close()
defer os.Remove("logging.log")
for _, val := range testValues {
err = l.Put(val)
if err != nil {
t.Error(err)
return
}
}
walker, err := NewFileWalker("logging.log")
if err != nil {
t.Error(err)
return
}
i := 0
for {
entries, err := walker.Next()
if err == io.EOF {
break
} else if err != nil {
t.Error(err)
return
}
if len(entries) != 1 {
t.Error("> 1 entry in log tx")
return
}
val := entries[0]
if !proto.Equal(val, testValues[i]) {
t.Error("proto message mismatch")
}
i += 1
}
}
// Check that we correctly catch CRC32 mismatches
func TestCRC32MismatchLog(t *testing.T) {
chunk, _, err := genTxValue(0xff, []byte{0xff, 0xff, 0xff, 0xff, 0xff})
if err != nil {
t.Error(err)
}
buf, err := genTestCaseHeader(chunk, 1, 0xcafebabe)
if err != nil {
t.Error(err)
}
walker, err := NewReaderWalker(buf)
if err != nil {
t.Error(err)
}
_, err = walker.Next()
if err != ErrCRC32Mismatch {
t.Errorf("exepcted CRC32 mismatch, got %v", err)
}
_, err = walker.Next()
if err != io.EOF {
t.Errorf("expected EOF, got %v", err)
}
}
// Test that unknown TxGroup values are not attempted to be
// decoded.
func TestUnknownTypeDecode(t *testing.T) {
buf, crc32sum, err := genTxValue(0xfa, []byte{0xfa, 0xfa, 0xfa})
if err != nil {
t.Error(err)
}
r, err := genTestCaseHeader(buf, 1, crc32sum)
if err != nil {
t.Error(err)
}
walker, err := NewReaderWalker(r)
if err != nil {
t.Error(err)
}
entries, err := walker.Next()
// The bytes above should not decode to anything useful
// (because they have an unknown type kind)
if len(entries) != 0 && err != nil {
t.Errorf("expected empty entries and non-nil err (got %v entries and %v)", len(entries), err)
}
_, err = walker.Next()
if err != io.EOF {
t.Errorf("expected EOF, got %v", err)
}
}
// Test a TxRecord with some trailing bytes
func TestTrailingBytesTxRecord(t *testing.T) {
buf, _, err := genTxValue(0xfa, []byte{0xff, 0xff, 0xff})
// Add some trailing bytes to the tx record
buf = append(buf, byte(0xff))
buf = append(buf, byte(0xff))
buf = append(buf, byte(0xff))
summer := crc32.NewIEEE()
_, err = summer.Write(buf)
if err != nil {
t.Error(err)
}
crc32sum := summer.Sum32()
r, err := genTestCaseHeader(buf, 1, crc32sum)
if err != nil {
t.Error(err)
}
walker, err := NewReaderWalker(r)
if err != nil {
t.Error(err)
}
_, err = walker.Next()
if err != ErrRemainingBytesForRecord {
t.Error(err)
}
_, err = walker.Next()
if err != io.EOF {
t.Errorf("expected EOF, got %v", err)
}
}
// Test that we check for TxRecords that are too big.
// A TxRecord can hold 255 entries, and each of those can be
// up to 16KB.
func TestTooBigTxRecord(t *testing.T) {
bigValue := make([]byte, math.MaxUint16*math.MaxUint8+4)
r, err := genTestCaseHeader(bigValue, 1, 0)
if err != nil {
t.Error(err)
}
walker, err := NewReaderWalker(r)
if err != nil {
t.Error(err)
}
_, err = walker.Next()
if err != ErrRecordTooBig {
t.Errorf("expected ErrRecordTooBig, got %v", err)
}
}
// Test that we correctly enforce the 255 entry limit of TxGroups.
func TestTxGroupCapacityEnforcement(t *testing.T) {
l, err := NewLogFile("capacity-enforcement.log")
if err != nil {
t.Error(err)
return
}
defer l.Close()
defer os.Remove("capacity-enforcement.log")
tx := l.BeginTx()
if err != nil {
t.Error(err)
}
for i := 0; i <= 255; i++ {
entry := testValues[i%len(testValues)]
err = tx.Put(entry)
if err != nil {
t.Error(err)
}
}
entry := testValues[0]
err = tx.Put(entry)
if err != ErrTxGroupFull {
t.Error(err)
}
}