diff options
author | Googler <noreply@google.com> | 2018-04-27 10:37:02 -0700 |
---|---|---|
committer | Adin Scannell <ascannell@google.com> | 2018-04-28 01:44:26 -0400 |
commit | d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch) | |
tree | 54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/state/state_test.go | |
parent | f70210e742919f40aa2f0934a22f1c9ba6dada62 (diff) |
Check in gVisor.
PiperOrigin-RevId: 194583126
Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/state/state_test.go')
-rw-r--r-- | pkg/state/state_test.go | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go new file mode 100644 index 000000000..d5a739f18 --- /dev/null +++ b/pkg/state/state_test.go @@ -0,0 +1,719 @@ +// Copyright 2018 Google Inc. +// +// 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" + "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(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(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", + }, + }, + { + 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(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(buf, bs, nil); err != nil { + b.Errorf("save failed: %v", err) + } + var stats Stats + b.StartTimer() + if err := Load(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, + }) +} |