tbotd/vendor/src/github.com/Sereal/Sereal/Go/sereal/sereal_test.go

905 lines
19 KiB
Go

package sereal
import (
"bytes"
"encoding/hex"
"errors"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
)
var roundtrips = []interface{}{
true,
false,
1,
10,
100,
200,
300,
0,
-1,
-15,
15,
-16,
16,
17,
-17,
-2613115362782646504,
uint(0xdbbc596c24396f18),
"hello",
"hello, world",
"twas brillig and the slithy toves and gyre and gimble in the wabe",
float32(2.2),
float32(9891234567890.098),
float64(2.2),
float64(9891234567890.098),
[]interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14},
[]interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
[]interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
[]interface{}{1, 100, 1000, 2000, 0xdeadbeef, float32(2.2), "hello, world", map[string]interface{}{"foo": []interface{}{1, 2, 3}}},
map[string]interface{}{"foo": 1, "bar": 2, "baz": "qux"},
}
func TestRoundtripGo(t *testing.T) {
testRoundtrip(t, false, 1)
testRoundtrip(t, false, 2)
}
func TestRoundtripPerl(t *testing.T) {
testRoundtrip(t, true, 1)
testRoundtrip(t, true, 2)
}
func testRoundtrip(t *testing.T, perlCompat bool, version int) {
e := &Encoder{PerlCompat: perlCompat, version: version}
d := &Decoder{PerlCompat: false}
for _, v := range roundtrips {
b, err := e.Marshal(v)
if err != nil {
t.Errorf("failed marshalling with perlCompat=%t : %v: %s\n", perlCompat, v, err)
}
var unp interface{}
err = d.Unmarshal(b, &unp)
if err != nil {
t.Errorf("perl compat: error during unmarshall: %s\n", err)
}
if !reflect.DeepEqual(v, unp) {
t.Errorf("failed roundtripping with perlCompat=%t: %#v: got %#v\n", perlCompat, v, unp)
}
}
}
func TestRoundtripCompat(t *testing.T) {
input := []interface{}{map[string]interface{}{"foo": []interface{}{1, 2, 3}}}
expectGo := []interface{}{map[string]interface{}{"foo": []interface{}{1, 2, 3}}}
expectPerlCompat := &[]interface{}{&map[string]interface{}{"foo": &[]interface{}{1, 2, 3}}}
e := &Encoder{}
d := &Decoder{}
noCompat, _ := e.Marshal(input)
e.PerlCompat = true
perlCompat, _ := e.Marshal(input)
// no perl compat on encode, no perl compat on decode
var nono interface{}
err := d.Unmarshal(noCompat, &nono)
if err != nil {
t.Errorf("perl compat: error during unmarshall: %s\n", err)
}
if !reflect.DeepEqual(expectGo, nono) {
t.Errorf("perl compat: no no failed: got %#v\n", nono)
}
// perl compat on encode, no perl compat on decode
var yesno interface{}
err = d.Unmarshal(perlCompat, &yesno)
if err != nil {
t.Errorf("perl compat: error during unmarshall: %s\n", err)
}
if !reflect.DeepEqual(expectGo, yesno) {
t.Errorf("perl compat: yes no failed: got %#v\n", yesno)
}
d.PerlCompat = true
// no perl compat on encode, perl compat on decode
var noyes interface{}
err = d.Unmarshal(noCompat, &noyes)
if err != nil {
t.Errorf("perl compat: error during unmarshall: %s\n", err)
}
if !reflect.DeepEqual(expectGo, noyes) {
t.Errorf("perl compat: no yes failed: got %#v\n", noyes)
}
// perl compat on encode, yes perl compat on decode
var yesyes interface{}
err = d.Unmarshal(perlCompat, &yesyes)
if err != nil {
t.Errorf("perl compat: error during unmarshall: %s\n", err)
}
if !reflect.DeepEqual(expectPerlCompat, yesyes) {
t.Errorf("perl compat: yes yes failed: got %#v\n", yesyes)
}
}
/*
* To make the corpus of test files:
* perl -I Perl/shared/t/lib/ -MSereal::TestSet -MSereal::Encoder -e'Sereal::TestSet::write_test_files("test_dir")'
*
* This runs the Decoder/Encoder over every file in the supplied directory and tells you when the bytes the encoder
* outputs do not match the bytes in the test file. The purpose is to check if roundtripping to Perl type
* datastructures works.
*
* If you pass a file as parameter it will do the same but do more detailed logging.
*
*/
func TestCorpus(t *testing.T) {
e := &Encoder{PerlCompat: true}
d := &Decoder{PerlCompat: true}
_ = e
debug := false
corpusFiles, err := filepath.Glob("test_dir/test_data_?????")
// corpusFiles, err = filepath.Glob("test_dir/test_data_00028")
// debug = true
if err != nil {
t.Errorf("error opening test_dir: %v", err)
return
}
for _, corpusFile := range corpusFiles {
contents, err := ioutil.ReadFile(corpusFile)
if err != nil {
t.Errorf("error opening test_dir/%s: %v", corpusFile, err)
return
}
var value interface{}
if debug {
t.Log("unmarshalling..")
t.Log(hex.Dump(contents))
}
err = d.Unmarshal(contents, &value)
if debug {
t.Log("done")
}
if err != nil {
t.Errorf("unpacking %s generated an error: %v", corpusFile, err)
continue
}
if debug {
t.Log("marshalling")
t.Log("value=", spew.Sdump(value))
t.Logf(" =%#v\n", value)
}
b, err := e.Marshal(value)
if debug {
t.Log("done")
t.Log(hex.Dump(b))
}
if err != nil {
t.Errorf("packing %s generated an error: %v", corpusFile, err)
continue
}
ioutil.WriteFile(corpusFile+"-go.out", b, 0600)
}
}
func TestSnappyArray(t *testing.T) {
e := &Encoder{}
d := &Decoder{}
// test many duplicated strings -- this uses both the string table and snappy compressiong
// this ensures we're not messing up the offsets when decoding
manydups := make([]string, 2048)
for i := 0; i < len(manydups); i++ {
manydups[i] = "hello, world " + strconv.Itoa(i%10)
}
encoded, err := e.Marshal(manydups)
if err != nil {
t.Errorf("encoding a large array generated an error: %v", err)
return
}
e.Compression = SnappyCompressor{Incremental: true}
e.CompressionThreshold = 0 // always compress
snencoded, err := e.Marshal(manydups)
if err != nil {
t.Fatalf("snappy encoding a large array generated an error: %v", err)
}
if len(encoded) <= len(snencoded) {
t.Fatalf("snappy failed to compress redundant array: encoded=%d snappy=%d\n", len(encoded), len(snencoded))
}
var decoded []string
err = d.Unmarshal(snencoded, &decoded)
if err != nil {
t.Fatalf("snappy decoding generated error: %v", err)
}
if len(decoded) != 2048 {
t.Fatalf("got wrong number of elements back: wanted=%d got=%d\n", len(manydups), len(decoded))
}
for i := 0; i < 2048; i++ {
s := decoded[i]
expected := "hello, world " + strconv.Itoa(i%10)
if s != expected {
t.Errorf("failed decompressing many-dup string: s=%s expected=%s", s, expected)
}
}
}
func TestStructs(t *testing.T) {
type A struct {
Name string
Phone string
Siblings int
Spouse bool
Money float64
}
// some people
Afoo := A{"mr foo", "12345", 10, true, 123.45}
Abar := A{"mr bar", "54321", 5, false, 321.45}
Abaz := A{"mr baz", "15243", 20, true, 543.21}
type nested1 struct {
Person A
}
type nested struct {
Nested1 nested1
}
type private struct {
pbool bool
pstr string
pint int
}
type semiprivate struct {
Bool bool
pbool bool
String string
pstr string
pint int
}
type ATags struct {
Name string `sereal:"Phone"`
Phone string `sereal:"Name"`
Siblings int // no tag, isn't unpacked
}
type ALowerTags struct {
Name string `sereal:"name"`
Phone string `sereal:"phone"`
}
tests := []struct {
what string
input interface{}
outvar interface{}
expected interface{}
}{
{
"struct with fields",
Afoo,
A{},
Afoo,
},
{
"struct with fields into map",
Afoo,
map[string]interface{}{},
map[string]interface{}{
"Name": "mr foo",
"Phone": "12345",
"Siblings": 10,
"Spouse": true,
"Money": 123.45,
},
},
{
"decode struct with tags",
Afoo,
ATags{},
ATags{Name: "12345", Phone: "mr foo", Siblings: 0},
},
{
"encode struct with tags",
ATags{Name: "12345", Phone: "mr foo", Siblings: 10},
A{},
A{Name: "mr foo", Phone: "12345"},
},
{
"decode struct with lower-case field names",
ALowerTags{Name: "mr foo", Phone: "12345"},
A{},
A{Name: "mr foo", Phone: "12345"},
},
{
"struct with private fields",
private{false, "hello", 3},
private{}, // zero value for struct
private{},
},
{
"semi-private struct",
semiprivate{Bool: true, pbool: false, String: "world", pstr: "hello", pint: 3},
semiprivate{},
semiprivate{Bool: true, String: "world"},
},
{
"nil slice of structs",
[]A{Afoo, Abar, Abaz},
[]A(nil),
[]A{Afoo, Abar, Abaz},
},
{
"0-length slice of structs",
[]A{Afoo, Abar, Abaz},
[]A{},
[]A{Afoo, Abar, Abaz},
},
{
"1-length slice of structs",
[]A{Afoo, Abar, Abaz},
[]A{A{}},
[]A{Afoo},
},
{
"nested",
nested{nested1{Afoo}},
nested{},
nested{nested1{Afoo}},
},
}
e := &Encoder{}
d := &Decoder{}
for _, v := range tests {
rinput := reflect.ValueOf(v.input)
x, err := e.Marshal(rinput.Interface())
if err != nil {
t.Errorf("error marshalling %s: %s\n", v.what, err)
continue
}
routvar := reflect.New(reflect.TypeOf(v.outvar))
routvar.Elem().Set(reflect.ValueOf(v.outvar))
err = d.Unmarshal(x, routvar.Interface())
if err != nil {
t.Errorf("error unmarshalling %s: %s\n", v.what, err)
continue
}
if !reflect.DeepEqual(routvar.Elem().Interface(), v.expected) {
t.Errorf("roundtrip mismatch for %s: got: %#v expected: %#v\n", v.what, routvar.Elem().Interface(), v.expected)
}
}
}
func TestDecodeToStruct(t *testing.T) {
type obj struct {
ValueStr string
ValueByte []byte
ValueInt int
ValueSlice []float32
ValueHash map[string][]byte
}
exp := make([]obj, 3)
exp[0] = obj{
ValueStr: "string as string value which actually should be 32+ characters",
ValueByte: []byte("string as binary value"),
ValueInt: 10,
ValueSlice: []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0},
ValueHash: map[string][]byte{
"key1": []byte("unique value"),
"key2": []byte("duplicate value"),
"key3": []byte("deplicate value"),
},
}
exp[1] = obj{
ValueStr: "another string as string value which actually should be 32+ characters",
ValueByte: []byte("another string as binary value"),
ValueInt: -10,
ValueSlice: []float32{18.0, 19.0, 20.0},
ValueHash: map[string][]byte{
"key1": []byte("unique value"),
"key2": []byte("duplicate value"),
"key3": []byte("deplicate value"),
},
}
exp[2] = exp[0]
filename := "test_dir/test-decode-struct.srl"
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Skip("run 'make test_files' and try again")
return
}
var slice []obj
d := NewDecoder()
if err := d.Unmarshal(content, &slice); err != nil {
t.Errorf("error unmarshalling: %s", err)
}
if !reflect.DeepEqual(exp, slice) {
t.Errorf("failed decode into struct:\n\nexp: %#v:\n\ngot %#v\n", exp, slice)
}
}
func TestStructsWithPtrs(t *testing.T) {
type First struct{ I int }
type Second struct{ S string }
type NestedPtr struct {
A *First
B *Second
}
tests := []struct {
what string
input interface{}
outvar interface{}
expected interface{}
}{
{
"struct with two fields of different types",
NestedPtr{&First{1}, &Second{"two"}},
NestedPtr{},
NestedPtr{&First{1}, &Second{"two"}},
},
{
"struct with two nils of different types",
NestedPtr{},
NestedPtr{},
NestedPtr{},
},
}
e := &Encoder{}
d := &Decoder{}
for _, v := range tests {
rinput := reflect.ValueOf(v.input)
x, err := e.Marshal(rinput.Interface())
if err != nil {
t.Errorf("error marshalling %s: %s\n", v.what, err)
continue
}
routvar := reflect.New(reflect.TypeOf(v.outvar))
routvar.Elem().Set(reflect.ValueOf(v.outvar))
err = d.Unmarshal(x, routvar.Interface())
if err != nil {
t.Errorf("error unmarshalling %s: %s\n", v.what, err)
continue
}
for i := 0; i < routvar.Elem().NumField(); i++ {
outfield := routvar.Elem().Field(i)
outfield.Interface()
expfield := reflect.ValueOf(v.expected).Field(i)
if !reflect.DeepEqual(outfield.Interface(), expfield.Interface()) {
t.Errorf("roundtrip mismatch for %s: got: %#v expected: %#v\n", v.what, outfield.Interface(), expfield.Interface())
}
}
}
}
type ErrorBinaryUnmarshaler int
var errUnmarshaler = errors.New("error binary unmarshaler")
func (e *ErrorBinaryUnmarshaler) UnmarshalBinary(data []byte) error {
return errUnmarshaler
}
func TestBinaryMarshaller(t *testing.T) {
// our data
now := time.Now()
e := &Encoder{}
d := &Decoder{}
x, err := e.Marshal(now)
if err != nil {
t.Errorf("error marshalling %s", err)
}
var tm time.Time
// unpack into something that expects the bytes
err = d.Unmarshal(x, &tm)
if err != nil {
t.Errorf("error unmarshalling: %s", err)
}
if !now.Equal(tm) {
t.Errorf("failed unpacking: got=%v wanted=%v\n", tm, now)
}
// unpack into something that produces an error
var errunmarshaler ErrorBinaryUnmarshaler
err = d.Unmarshal(x, &errunmarshaler)
if err == nil {
t.Errorf("failed propagating error from unmarshaler")
}
// unpack into something that isn't a marshaller
var i int
err = d.Unmarshal(x, &i)
if err == nil {
t.Errorf("failed to generate error trying to unpack into non-slice/unmashaler")
}
// unpack into a byte slice
bdata, _ := now.MarshalBinary()
var data []byte
err = d.Unmarshal(x, &data)
if !bytes.Equal(bdata, data) {
t.Errorf("failed unpacking into byte-slice: got=%v wanted=%v\n", tm, now)
}
// unpack into a nil interface
var intf interface{}
err = d.Unmarshal(x, &intf)
var pfreeze *PerlFreeze
var ok bool
if pfreeze, ok = intf.(*PerlFreeze); !ok {
t.Errorf("failed unpacking into nil interface : got=%v", intf)
}
if pfreeze.Class != "time.Time" || !bytes.Equal(pfreeze.Data, bdata) {
t.Errorf("failed unpacking into nil interface : got=%v", pfreeze)
}
// check that registering a type works
var registerTime time.Time
d.RegisterName("time.Time", &registerTime)
// unpack into a nil interface should return a time.Time
var tintf interface{}
err = d.Unmarshal(x, &tintf)
if err != nil {
t.Errorf("error unpacking registered type: %s", err)
}
var rtime *time.Time
if rtime, ok = tintf.(*time.Time); ok {
if !now.Equal(*rtime) {
t.Errorf("failed unpacking registered type: got=%v wanted=%v\n", rtime, now)
}
} else {
t.Errorf("failed unpacking registered nil interface : got=%v", tintf)
}
// overwrite with our error type
d.RegisterName("time.Time", &errunmarshaler)
var eintf interface{}
err = d.Unmarshal(x, &eintf)
if err != errUnmarshaler {
t.Errorf("failed to error unpacking registered error type: %s", err)
}
}
func TestUnmarshalHeaderError(t *testing.T) {
testcases := []struct {
docHex string
err error
}{
// Garbage
{"badbadbadbad", ErrBadHeader},
// Version 1 and 2, "=srl"
{"3d73726c0100", nil},
{"3d73726c0200", nil},
// Version 3, "=srl" with a high-bit-set-on-the-"s"
{"3df3726c0300", nil},
// Version 3, "=srl" corrupted by accidental UTF8 encoding
{"3dc3b3726c0300", ErrBadHeaderUTF8},
// Forbidden version 2 and high-bit-set-on-the-"s" combination
{"3df3726c0200", ErrBadHeader},
// Forbidden version 3 and obsolete "=srl" magic string
{"3d73726c0300", ErrBadHeader},
// Non-existing (yet) version 4, "=srl" with a high-bit-set-on-the-"s"
{"3df3726c0400", errors.New("document version '4' not yet supported")},
}
d := NewDecoder()
for i, tc := range testcases {
doc, err := hex.DecodeString(tc.docHex)
if err != nil {
t.Error(err)
continue
}
got := d.UnmarshalHeaderBody(doc, nil, nil)
wanted := tc.err
ok := false
ok = ok || (got == nil && wanted == nil)
ok = ok || (got != nil && wanted != nil && got.Error() == wanted.Error())
if !ok {
t.Errorf("test case #%v:\ngot : %v\nwanted: %v", i, got, wanted)
continue
}
}
}
func TestPrepareFreezeRoundtrip(t *testing.T) {
_, err := os.Stat("test_freeze")
if os.IsNotExist(err) {
return
}
now := time.Now()
type StructWithTime struct{ time.Time }
tests := []struct {
what string
input interface{}
outvar interface{}
expected interface{}
}{
{
"Time",
now,
time.Time{},
now,
},
{
"Time_ptr",
&now,
&time.Time{},
&now,
},
{
"struct_Time",
StructWithTime{now},
StructWithTime{},
StructWithTime{now},
},
{
"struct_Time_ptr",
&StructWithTime{now},
&StructWithTime{},
&StructWithTime{now},
},
}
for _, compat := range []bool{false, true} {
for _, v := range tests {
e := Encoder{PerlCompat: compat}
d := Decoder{}
var name string
if compat {
name = "compat_" + v.what
} else {
name = v.what
}
rinput := reflect.ValueOf(v.input)
x, err := e.Marshal(rinput.Interface())
if err != nil {
t.Errorf("error marshalling %s: %s\n", v.what, err)
continue
}
err = ioutil.WriteFile("test_freeze/"+name+"-go.out", x, 0600)
if err != nil {
t.Error(err)
}
routvar := reflect.New(reflect.TypeOf(v.outvar))
routvar.Elem().Set(reflect.ValueOf(v.outvar))
err = d.Unmarshal(x, routvar.Interface())
if err != nil {
t.Errorf("error unmarshalling %s: %s\n", v.what, err)
continue
}
if !reflect.DeepEqual(routvar.Elem().Interface(), v.expected) {
t.Errorf("roundtrip mismatch for %s: got: %#v expected: %#v\n", v.what, routvar.Elem().Interface(), v.expected)
}
}
}
}
func TestFreezeRoundtrip(t *testing.T) {
if os.Getenv("RUN_FREEZE") == "1" {
d := Decoder{}
buf, err := ioutil.ReadFile("test_freeze/Time-go.out")
if err != nil {
t.Error(err)
}
var then time.Time
d.Unmarshal(buf, &then)
type StructWithTime struct{ time.Time }
tests := []struct {
what string
outvar interface{}
expected interface{}
}{
{
"Time",
time.Time{},
then,
},
{
"Time_ptr",
&time.Time{},
&then,
},
{
"struct_Time",
StructWithTime{},
StructWithTime{then},
},
{
"struct_Time_ptr",
&StructWithTime{},
&StructWithTime{then},
},
}
for _, v := range tests {
for _, compat := range []string{"", "compat_"} {
x, err := ioutil.ReadFile("test_freeze/" + compat + v.what + "-perl.out")
if err != nil {
t.Error(err)
}
routvar := reflect.New(reflect.TypeOf(v.outvar))
routvar.Elem().Set(reflect.ValueOf(v.outvar))
err = d.Unmarshal(x, routvar.Interface())
if err != nil {
t.Errorf("error unmarshalling %s: %s\n", v.what, err)
continue
}
if !reflect.DeepEqual(routvar.Elem().Interface(), v.expected) {
t.Errorf("roundtrip mismatch for %s: got: %#v expected: %#v\n", v.what, routvar.Elem().Interface(), v.expected)
}
}
}
}
}
func TestIssue130(t *testing.T) {
t.Skip("Issue 130")
type AStructType struct {
EmptySlice []*AStructType
EmptySlice2 []AStructType
}
t1 := &AStructType{}
b, err := Marshal(t1)
if err != nil {
t.Fatal("failed to marshal:", err)
}
t12 := &AStructType{}
err = Unmarshal(b, &t12)
if err != nil {
t.Fatal("failed to unmarshal:", err)
}
if !reflect.DeepEqual(t1, t12) {
t.Errorf("roundtrip slice pointers failed\nwant\n%#v\ngot\n%#v", t1, t12)
}
}
func TestIssue131(t *testing.T) {
type A struct {
T *time.Time
}
t0 := time.Now()
a := A{T: &t0}
b, err := Marshal(&a)
if err != nil {
t.Fatal(err)
}
var decoded A
err = Unmarshal(b, &decoded)
if err != nil {
t.Fatal(err)
}
}
func TestIssue135(t *testing.T) {
type A struct {
M map[string][]int
}
u := A{M: make(map[string][]int)}
u.M["k99"] = []int{1, 2, 3}
b, err := Marshal(&u)
if err != nil {
t.Fatal(err)
}
var decoded A
err = Unmarshal(b, &decoded)
if err != nil {
t.Fatal(err)
}
}