// Copyright 2018 The gVisor Authors. // // 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" "context" "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)) if err := Save(context.Background(), saveBuffer, saveObjectPtr.Interface(), nil); err != nil && !test.Fail { 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)) if err := Load(context.Background(), bytes.NewReader(saveBuffer.Bytes()), loadObjectPtr.Interface(), nil); err != nil && !test.Fail { 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", "\xa0", }, }, { 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() if err := Save(context.Background(), ioutil.Discard, bs, &stats); err != nil { 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{} if err := Save(context.Background(), buf, bs, nil); err != nil { b.Errorf("save failed: %v", err) } var stats Stats b.StartTimer() if err := Load(context.Background(), buf, &newBS, &stats); err != nil { 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, }) }