2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-04-27 17:37:02 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-11-01 01:02:04 +00:00
|
|
|
"context"
|
2018-04-27 17:37:02 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"math"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestCase is used to define a single success/failure testcase of
|
|
|
|
// serialization of a set of objects.
|
|
|
|
type TestCase struct {
|
|
|
|
// Name is the name of the test case.
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// Objects is the list of values to serialize.
|
|
|
|
Objects []interface{}
|
|
|
|
|
|
|
|
// Fail is whether the test case is supposed to fail or not.
|
|
|
|
Fail bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// runTest runs all testcases.
|
|
|
|
func runTest(t *testing.T, tests []TestCase) {
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Logf("TEST %s:", test.Name)
|
|
|
|
for i, root := range test.Objects {
|
|
|
|
t.Logf(" case#%d: %#v", i, root)
|
|
|
|
|
|
|
|
// Save the passed object.
|
|
|
|
saveBuffer := &bytes.Buffer{}
|
|
|
|
saveObjectPtr := reflect.New(reflect.TypeOf(root))
|
|
|
|
saveObjectPtr.Elem().Set(reflect.ValueOf(root))
|
2019-11-01 01:02:04 +00:00
|
|
|
if err := Save(context.Background(), saveBuffer, saveObjectPtr.Interface(), nil); err != nil && !test.Fail {
|
2018-04-27 17:37:02 +00:00
|
|
|
t.Errorf(" FAIL: Save failed unexpectedly: %v", err)
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
t.Logf(" PASS: Save failed as expected: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load a new copy of the object.
|
|
|
|
loadObjectPtr := reflect.New(reflect.TypeOf(root))
|
2019-11-01 01:02:04 +00:00
|
|
|
if err := Load(context.Background(), bytes.NewReader(saveBuffer.Bytes()), loadObjectPtr.Interface(), nil); err != nil && !test.Fail {
|
2018-04-27 17:37:02 +00:00
|
|
|
t.Errorf(" FAIL: Load failed unexpectedly: %v", err)
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
t.Logf(" PASS: Load failed as expected: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare the values.
|
|
|
|
loadedValue := loadObjectPtr.Elem().Interface()
|
|
|
|
if eq := reflect.DeepEqual(root, loadedValue); !eq && !test.Fail {
|
|
|
|
t.Errorf(" FAIL: Objects differs; got %#v", loadedValue)
|
|
|
|
continue
|
|
|
|
} else if !eq {
|
|
|
|
t.Logf(" PASS: Object different as expected.")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything went okay. Is that good?
|
|
|
|
if test.Fail {
|
|
|
|
t.Errorf(" FAIL: Unexpected success.")
|
|
|
|
} else {
|
|
|
|
t.Logf(" PASS: Success.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// dumbStruct is a struct which does not implement the loader/saver interface.
|
|
|
|
// We expect that serialization of this struct will fail.
|
|
|
|
type dumbStruct struct {
|
|
|
|
A int
|
|
|
|
B int
|
|
|
|
}
|
|
|
|
|
|
|
|
// smartStruct is a struct which does implement the loader/saver interface.
|
|
|
|
// We expect that serialization of this struct will succeed.
|
|
|
|
type smartStruct struct {
|
|
|
|
A int
|
|
|
|
B int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smartStruct) save(m Map) {
|
|
|
|
m.Save("A", &s.A)
|
|
|
|
m.Save("B", &s.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *smartStruct) load(m Map) {
|
|
|
|
m.Load("A", &s.A)
|
|
|
|
m.Load("B", &s.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
// valueLoadStruct uses a value load.
|
|
|
|
type valueLoadStruct struct {
|
|
|
|
v int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *valueLoadStruct) save(m Map) {
|
|
|
|
m.SaveValue("v", v.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *valueLoadStruct) load(m Map) {
|
|
|
|
m.LoadValue("v", new(int), func(value interface{}) {
|
|
|
|
v.v = value.(int)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// afterLoadStruct has an AfterLoad function.
|
|
|
|
type afterLoadStruct struct {
|
|
|
|
v int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *afterLoadStruct) save(m Map) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *afterLoadStruct) load(m Map) {
|
|
|
|
m.AfterLoad(func() {
|
|
|
|
a.v++
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// genericContainer is a generic dispatcher.
|
|
|
|
type genericContainer struct {
|
|
|
|
v interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *genericContainer) save(m Map) {
|
|
|
|
m.Save("v", &g.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *genericContainer) load(m Map) {
|
|
|
|
m.Load("v", &g.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// sliceContainer is a generic slice.
|
|
|
|
type sliceContainer struct {
|
|
|
|
v []interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sliceContainer) save(m Map) {
|
|
|
|
m.Save("v", &s.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *sliceContainer) load(m Map) {
|
|
|
|
m.Load("v", &s.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// mapContainer is a generic map.
|
|
|
|
type mapContainer struct {
|
|
|
|
v map[int]interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mc *mapContainer) save(m Map) {
|
|
|
|
m.Save("v", &mc.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mc *mapContainer) load(m Map) {
|
|
|
|
// Some of the test cases below assume legacy behavior wherein maps
|
|
|
|
// will automatically inherit dependencies.
|
|
|
|
m.LoadWait("v", &mc.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// dumbMap is a map which does not implement the loader/saver interface.
|
|
|
|
// Serialization of this map will default to the standard encode/decode logic.
|
|
|
|
type dumbMap map[string]int
|
|
|
|
|
|
|
|
// pointerStruct contains various pointers, shared and non-shared, and pointers
|
|
|
|
// to pointers. We expect that serialization will respect the structure.
|
|
|
|
type pointerStruct struct {
|
|
|
|
A *int
|
|
|
|
B *int
|
|
|
|
C *int
|
|
|
|
D *int
|
|
|
|
|
|
|
|
AA **int
|
|
|
|
BB **int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *pointerStruct) save(m Map) {
|
|
|
|
m.Save("A", &p.A)
|
|
|
|
m.Save("B", &p.B)
|
|
|
|
m.Save("C", &p.C)
|
|
|
|
m.Save("D", &p.D)
|
|
|
|
m.Save("AA", &p.AA)
|
|
|
|
m.Save("BB", &p.BB)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *pointerStruct) load(m Map) {
|
|
|
|
m.Load("A", &p.A)
|
|
|
|
m.Load("B", &p.B)
|
|
|
|
m.Load("C", &p.C)
|
|
|
|
m.Load("D", &p.D)
|
|
|
|
m.Load("AA", &p.AA)
|
|
|
|
m.Load("BB", &p.BB)
|
|
|
|
}
|
|
|
|
|
|
|
|
// testInterface is a trivial interface example.
|
|
|
|
type testInterface interface {
|
|
|
|
Foo()
|
|
|
|
}
|
|
|
|
|
|
|
|
// testImpl is a trivial implementation of testInterface.
|
|
|
|
type testImpl struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Foo satisfies testInterface.
|
|
|
|
func (t *testImpl) Foo() {
|
|
|
|
}
|
|
|
|
|
|
|
|
// testImpl is trivially serializable.
|
|
|
|
func (t *testImpl) save(m Map) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// testImpl is trivially serializable.
|
|
|
|
func (t *testImpl) load(m Map) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// testI demonstrates interface dispatching.
|
|
|
|
type testI struct {
|
|
|
|
I testInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testI) save(m Map) {
|
|
|
|
m.Save("I", &t.I)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testI) load(m Map) {
|
|
|
|
m.Load("I", &t.I)
|
|
|
|
}
|
|
|
|
|
|
|
|
// cycleStruct is used to implement basic cycles.
|
|
|
|
type cycleStruct struct {
|
|
|
|
c *cycleStruct
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cycleStruct) save(m Map) {
|
|
|
|
m.Save("c", &c.c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cycleStruct) load(m Map) {
|
|
|
|
m.Load("c", &c.c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// badCycleStruct actually has deadlocking dependencies.
|
|
|
|
//
|
|
|
|
// This should pass if b.b = {nil|b} and fail otherwise.
|
|
|
|
type badCycleStruct struct {
|
|
|
|
b *badCycleStruct
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *badCycleStruct) save(m Map) {
|
|
|
|
m.Save("b", &b.b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *badCycleStruct) load(m Map) {
|
|
|
|
m.LoadWait("b", &b.b)
|
|
|
|
m.AfterLoad(func() {
|
|
|
|
// This is not executable, since AfterLoad requires that the
|
|
|
|
// object and all dependencies are complete. This should cause
|
|
|
|
// a deadlock error during load.
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// emptyStructPointer points to an empty struct.
|
|
|
|
type emptyStructPointer struct {
|
|
|
|
nothing *struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *emptyStructPointer) save(m Map) {
|
|
|
|
m.Save("nothing", &e.nothing)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *emptyStructPointer) load(m Map) {
|
|
|
|
m.Load("nothing", &e.nothing)
|
|
|
|
}
|
|
|
|
|
|
|
|
// truncateInteger truncates an integer.
|
|
|
|
type truncateInteger struct {
|
|
|
|
v int64
|
|
|
|
v2 int32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateInteger) save(m Map) {
|
|
|
|
t.v2 = int32(t.v)
|
|
|
|
m.Save("v", &t.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateInteger) load(m Map) {
|
|
|
|
m.Load("v", &t.v2)
|
|
|
|
t.v = int64(t.v2)
|
|
|
|
}
|
|
|
|
|
|
|
|
// truncateUnsignedInteger truncates an unsigned integer.
|
|
|
|
type truncateUnsignedInteger struct {
|
|
|
|
v uint64
|
|
|
|
v2 uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateUnsignedInteger) save(m Map) {
|
|
|
|
t.v2 = uint32(t.v)
|
|
|
|
m.Save("v", &t.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateUnsignedInteger) load(m Map) {
|
|
|
|
m.Load("v", &t.v2)
|
|
|
|
t.v = uint64(t.v2)
|
|
|
|
}
|
|
|
|
|
|
|
|
// truncateFloat truncates a floating point number.
|
|
|
|
type truncateFloat struct {
|
|
|
|
v float64
|
|
|
|
v2 float32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateFloat) save(m Map) {
|
|
|
|
t.v2 = float32(t.v)
|
|
|
|
m.Save("v", &t.v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *truncateFloat) load(m Map) {
|
|
|
|
m.Load("v", &t.v2)
|
|
|
|
t.v = float64(t.v2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestTypes(t *testing.T) {
|
|
|
|
// x and y are basic integers, while xp points to x.
|
|
|
|
x := 1
|
|
|
|
y := 2
|
|
|
|
xp := &x
|
|
|
|
|
|
|
|
// cs is a single object cycle.
|
|
|
|
cs := cycleStruct{nil}
|
|
|
|
cs.c = &cs
|
|
|
|
|
|
|
|
// cs1 and cs2 are in a two object cycle.
|
|
|
|
cs1 := cycleStruct{nil}
|
|
|
|
cs2 := cycleStruct{nil}
|
|
|
|
cs1.c = &cs2
|
|
|
|
cs2.c = &cs1
|
|
|
|
|
|
|
|
// bs is a single object cycle.
|
|
|
|
bs := badCycleStruct{nil}
|
|
|
|
bs.b = &bs
|
|
|
|
|
|
|
|
// bs2 and bs2 are in a deadlocking cycle.
|
|
|
|
bs1 := badCycleStruct{nil}
|
|
|
|
bs2 := badCycleStruct{nil}
|
|
|
|
bs1.b = &bs2
|
|
|
|
bs2.b = &bs1
|
|
|
|
|
|
|
|
// regular nils.
|
|
|
|
var (
|
|
|
|
nilmap dumbMap
|
|
|
|
nilslice []byte
|
|
|
|
)
|
|
|
|
|
|
|
|
// embed points to embedded fields.
|
|
|
|
embed1 := pointerStruct{}
|
|
|
|
embed1.AA = &embed1.A
|
|
|
|
embed2 := pointerStruct{}
|
|
|
|
embed2.BB = &embed2.B
|
|
|
|
|
|
|
|
// es1 contains two structs pointing to the same empty struct.
|
|
|
|
es := emptyStructPointer{new(struct{})}
|
|
|
|
es1 := []emptyStructPointer{es, es}
|
|
|
|
|
|
|
|
tests := []TestCase{
|
|
|
|
{
|
|
|
|
Name: "bool",
|
|
|
|
Objects: []interface{}{
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "integers",
|
|
|
|
Objects: []interface{}{
|
|
|
|
int(0),
|
|
|
|
int(1),
|
|
|
|
int(-1),
|
|
|
|
int8(0),
|
|
|
|
int8(1),
|
|
|
|
int8(-1),
|
|
|
|
int16(0),
|
|
|
|
int16(1),
|
|
|
|
int16(-1),
|
|
|
|
int32(0),
|
|
|
|
int32(1),
|
|
|
|
int32(-1),
|
|
|
|
int64(0),
|
|
|
|
int64(1),
|
|
|
|
int64(-1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "unsigned integers",
|
|
|
|
Objects: []interface{}{
|
|
|
|
uint(0),
|
|
|
|
uint(1),
|
|
|
|
uint8(0),
|
|
|
|
uint8(1),
|
|
|
|
uint16(0),
|
|
|
|
uint16(1),
|
|
|
|
uint32(1),
|
|
|
|
uint64(0),
|
|
|
|
uint64(1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "strings",
|
|
|
|
Objects: []interface{}{
|
|
|
|
"",
|
|
|
|
"foo",
|
|
|
|
"bar",
|
2018-05-09 00:23:06 +00:00
|
|
|
"\xa0",
|
2018-04-27 17:37:02 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "slices",
|
|
|
|
Objects: []interface{}{
|
|
|
|
[]int{-1, 0, 1},
|
|
|
|
[]*int{&x, &x, &x},
|
|
|
|
[]int{1, 2, 3}[0:1],
|
|
|
|
[]int{1, 2, 3}[1:2],
|
|
|
|
make([]byte, 32),
|
|
|
|
make([]byte, 32)[:16],
|
|
|
|
make([]byte, 32)[:16:20],
|
|
|
|
nilslice,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "arrays",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&[1048576]bool{false, true, false, true},
|
|
|
|
&[1048576]uint8{0, 1, 2, 3},
|
|
|
|
&[1048576]byte{0, 1, 2, 3},
|
|
|
|
&[1048576]uint16{0, 1, 2, 3},
|
|
|
|
&[1048576]uint{0, 1, 2, 3},
|
|
|
|
&[1048576]uint32{0, 1, 2, 3},
|
|
|
|
&[1048576]uint64{0, 1, 2, 3},
|
|
|
|
&[1048576]uintptr{0, 1, 2, 3},
|
|
|
|
&[1048576]int8{0, -1, -2, -3},
|
|
|
|
&[1048576]int16{0, -1, -2, -3},
|
|
|
|
&[1048576]int32{0, -1, -2, -3},
|
|
|
|
&[1048576]int64{0, -1, -2, -3},
|
|
|
|
&[1048576]float32{0, 1.1, 2.2, 3.3},
|
|
|
|
&[1048576]float64{0, 1.1, 2.2, 3.3},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "pointers",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&pointerStruct{A: &x, B: &x, C: &y, D: &y, AA: &xp, BB: &xp},
|
|
|
|
&pointerStruct{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "empty struct",
|
|
|
|
Objects: []interface{}{
|
|
|
|
struct{}{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "unenlightened structs",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&dumbStruct{A: 1, B: 2},
|
|
|
|
},
|
|
|
|
Fail: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "enlightened structs",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&smartStruct{A: 1, B: 2},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "load-hooks",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&afterLoadStruct{v: 1},
|
|
|
|
&valueLoadStruct{v: 1},
|
|
|
|
&genericContainer{v: &afterLoadStruct{v: 1}},
|
|
|
|
&genericContainer{v: &valueLoadStruct{v: 1}},
|
|
|
|
&sliceContainer{v: []interface{}{&afterLoadStruct{v: 1}}},
|
|
|
|
&sliceContainer{v: []interface{}{&valueLoadStruct{v: 1}}},
|
|
|
|
&mapContainer{v: map[int]interface{}{0: &afterLoadStruct{v: 1}}},
|
|
|
|
&mapContainer{v: map[int]interface{}{0: &valueLoadStruct{v: 1}}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "maps",
|
|
|
|
Objects: []interface{}{
|
|
|
|
dumbMap{"a": -1, "b": 0, "c": 1},
|
|
|
|
map[smartStruct]int{{}: 0, {A: 1}: 1},
|
|
|
|
nilmap,
|
|
|
|
&mapContainer{v: map[int]interface{}{0: &smartStruct{A: 1}}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "interfaces",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&testI{&testImpl{}},
|
|
|
|
&testI{nil},
|
|
|
|
&testI{(*testImpl)(nil)},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "unregistered-interfaces",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&genericContainer{v: afterLoadStruct{v: 1}},
|
|
|
|
&genericContainer{v: valueLoadStruct{v: 1}},
|
|
|
|
&sliceContainer{v: []interface{}{afterLoadStruct{v: 1}}},
|
|
|
|
&sliceContainer{v: []interface{}{valueLoadStruct{v: 1}}},
|
|
|
|
&mapContainer{v: map[int]interface{}{0: afterLoadStruct{v: 1}}},
|
|
|
|
&mapContainer{v: map[int]interface{}{0: valueLoadStruct{v: 1}}},
|
|
|
|
},
|
|
|
|
Fail: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "cycles",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&cs,
|
|
|
|
&cs1,
|
|
|
|
&cycleStruct{&cs1},
|
|
|
|
&cycleStruct{&cs},
|
|
|
|
&badCycleStruct{nil},
|
|
|
|
&bs,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "deadlock",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&bs1,
|
|
|
|
},
|
|
|
|
Fail: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "embed",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&embed1,
|
|
|
|
&embed2,
|
|
|
|
},
|
|
|
|
Fail: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "empty structs",
|
|
|
|
Objects: []interface{}{
|
|
|
|
new(struct{}),
|
|
|
|
es,
|
|
|
|
es1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "truncated okay",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&truncateInteger{v: 1},
|
|
|
|
&truncateUnsignedInteger{v: 1},
|
|
|
|
&truncateFloat{v: 1.0},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "truncated bad",
|
|
|
|
Objects: []interface{}{
|
|
|
|
&truncateInteger{v: math.MaxInt32 + 1},
|
|
|
|
&truncateUnsignedInteger{v: math.MaxUint32 + 1},
|
|
|
|
&truncateFloat{v: math.MaxFloat32 * 2},
|
|
|
|
},
|
|
|
|
Fail: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
runTest(t, tests)
|
|
|
|
}
|
|
|
|
|
|
|
|
// benchStruct is used for benchmarking.
|
|
|
|
type benchStruct struct {
|
|
|
|
b *benchStruct
|
|
|
|
|
|
|
|
// Dummy data is included to ensure that these objects are large.
|
|
|
|
// This is to detect possible regression when registering objects.
|
|
|
|
_ [4096]byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *benchStruct) save(m Map) {
|
|
|
|
m.Save("b", &b.b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *benchStruct) load(m Map) {
|
|
|
|
m.LoadWait("b", &b.b)
|
|
|
|
m.AfterLoad(b.afterLoad)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *benchStruct) afterLoad() {
|
|
|
|
// Do nothing, just force scheduling.
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildObject builds a benchmark object.
|
|
|
|
func buildObject(n int) (b *benchStruct) {
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
b = &benchStruct{b: b}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEncoding(b *testing.B) {
|
|
|
|
b.StopTimer()
|
|
|
|
bs := buildObject(b.N)
|
|
|
|
var stats Stats
|
|
|
|
b.StartTimer()
|
2019-11-01 01:02:04 +00:00
|
|
|
if err := Save(context.Background(), ioutil.Discard, bs, &stats); err != nil {
|
2018-04-27 17:37:02 +00:00
|
|
|
b.Errorf("save failed: %v", err)
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
if b.N > 1000 {
|
|
|
|
b.Logf("breakdown (n=%d): %s", b.N, &stats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkDecoding(b *testing.B) {
|
|
|
|
b.StopTimer()
|
|
|
|
bs := buildObject(b.N)
|
|
|
|
var newBS benchStruct
|
|
|
|
buf := &bytes.Buffer{}
|
2019-11-01 01:02:04 +00:00
|
|
|
if err := Save(context.Background(), buf, bs, nil); err != nil {
|
2018-04-27 17:37:02 +00:00
|
|
|
b.Errorf("save failed: %v", err)
|
|
|
|
}
|
|
|
|
var stats Stats
|
|
|
|
b.StartTimer()
|
2019-11-01 01:02:04 +00:00
|
|
|
if err := Load(context.Background(), buf, &newBS, &stats); err != nil {
|
2018-04-27 17:37:02 +00:00
|
|
|
b.Errorf("load failed: %v", err)
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
if b.N > 1000 {
|
|
|
|
b.Logf("breakdown (n=%d): %s", b.N, &stats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Register("stateTest.smartStruct", (*smartStruct)(nil), Fns{
|
|
|
|
Save: (*smartStruct).save,
|
|
|
|
Load: (*smartStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.afterLoadStruct", (*afterLoadStruct)(nil), Fns{
|
|
|
|
Save: (*afterLoadStruct).save,
|
|
|
|
Load: (*afterLoadStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.valueLoadStruct", (*valueLoadStruct)(nil), Fns{
|
|
|
|
Save: (*valueLoadStruct).save,
|
|
|
|
Load: (*valueLoadStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.genericContainer", (*genericContainer)(nil), Fns{
|
|
|
|
Save: (*genericContainer).save,
|
|
|
|
Load: (*genericContainer).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.sliceContainer", (*sliceContainer)(nil), Fns{
|
|
|
|
Save: (*sliceContainer).save,
|
|
|
|
Load: (*sliceContainer).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.mapContainer", (*mapContainer)(nil), Fns{
|
|
|
|
Save: (*mapContainer).save,
|
|
|
|
Load: (*mapContainer).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.pointerStruct", (*pointerStruct)(nil), Fns{
|
|
|
|
Save: (*pointerStruct).save,
|
|
|
|
Load: (*pointerStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.testImpl", (*testImpl)(nil), Fns{
|
|
|
|
Save: (*testImpl).save,
|
|
|
|
Load: (*testImpl).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.testI", (*testI)(nil), Fns{
|
|
|
|
Save: (*testI).save,
|
|
|
|
Load: (*testI).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.cycleStruct", (*cycleStruct)(nil), Fns{
|
|
|
|
Save: (*cycleStruct).save,
|
|
|
|
Load: (*cycleStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.badCycleStruct", (*badCycleStruct)(nil), Fns{
|
|
|
|
Save: (*badCycleStruct).save,
|
|
|
|
Load: (*badCycleStruct).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.emptyStructPointer", (*emptyStructPointer)(nil), Fns{
|
|
|
|
Save: (*emptyStructPointer).save,
|
|
|
|
Load: (*emptyStructPointer).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.truncateInteger", (*truncateInteger)(nil), Fns{
|
|
|
|
Save: (*truncateInteger).save,
|
|
|
|
Load: (*truncateInteger).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.truncateUnsignedInteger", (*truncateUnsignedInteger)(nil), Fns{
|
|
|
|
Save: (*truncateUnsignedInteger).save,
|
|
|
|
Load: (*truncateUnsignedInteger).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.truncateFloat", (*truncateFloat)(nil), Fns{
|
|
|
|
Save: (*truncateFloat).save,
|
|
|
|
Load: (*truncateFloat).load,
|
|
|
|
})
|
|
|
|
Register("stateTest.benchStruct", (*benchStruct)(nil), Fns{
|
|
|
|
Save: (*benchStruct).save,
|
|
|
|
Load: (*benchStruct).load,
|
|
|
|
})
|
|
|
|
}
|