Changing the implementation of DeepCopy to use reflection instead of Gob encode/decode.

pull/6/head
Kris Rousey 2015-05-13 10:12:36 -07:00
parent 3481db8aee
commit 4d031abc16
3 changed files with 154 additions and 38 deletions

52
pkg/api/copy_test.go Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 api_test
import (
"math/rand"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
apitesting "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
)
func TestDeepCopyApiObjects(t *testing.T) {
for i := 0; i < *fuzzIters; i++ {
for _, version := range []string{"", testapi.Version()} {
f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63()))
for kind := range api.Scheme.KnownTypes(version) {
item, err := api.Scheme.New(version, kind)
if err != nil {
t.Fatalf("Could not create a %s: %s", kind, err)
}
f.Fuzz(item)
itemCopy, err := conversion.DeepCopy(item)
if err != nil {
t.Errorf("Could not deep copy a %s: %s", kind, err)
continue
}
if !reflect.DeepEqual(item, itemCopy) {
t.Errorf("expected %#v\ngot %#v", item, itemCopy)
}
}
}
}
}

View File

@ -17,52 +17,98 @@ limitations under the License.
package conversion
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"sync"
)
// pool is a pool of copiers
var pool = sync.Pool{
New: func() interface{} { return newGobCopier() },
}
// DeepCopy makes a deep copy of source or returns an error.
func DeepCopy(source interface{}) (interface{}, error) {
v := reflect.New(reflect.TypeOf(source))
c := pool.Get().(gobCopier)
defer pool.Put(c)
if err := c.CopyInto(v.Interface(), source); err != nil {
return nil, err
v, err := deepCopy(reflect.ValueOf(source))
return v.Interface(), err
}
return v.Elem().Interface(), nil
}
func deepCopy(src reflect.Value) (reflect.Value, error) {
switch src.Kind() {
case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Uintptr:
return src, fmt.Errorf("cannot deep copy kind: %s", src.Kind())
// gobCopier provides a copy mechanism for objects using Gob.
// This object is not safe for multiple threads because buffer
// is shared.
type gobCopier struct {
enc *gob.Encoder
dec *gob.Decoder
case reflect.Array:
dst := reflect.New(src.Type())
for i := 0; i < src.Len(); i++ {
copyVal, err := deepCopy(src.Index(i))
if err != nil {
return src, err
}
dst.Elem().Index(i).Set(copyVal)
}
return dst.Elem(), nil
func newGobCopier() gobCopier {
buf := &bytes.Buffer{}
return gobCopier{
enc: gob.NewEncoder(buf),
dec: gob.NewDecoder(buf),
}
case reflect.Interface:
if src.IsNil() {
return src, nil
}
return deepCopy(src.Elem())
func (c *gobCopier) CopyInto(dst, src interface{}) error {
if err := c.enc.Encode(src); err != nil {
return err
case reflect.Map:
if src.IsNil() {
return src, nil
}
if err := c.dec.Decode(dst); err != nil {
return err
dst := reflect.MakeMap(src.Type())
for _, k := range src.MapKeys() {
copyVal, err := deepCopy(src.MapIndex(k))
if err != nil {
return src, err
}
dst.SetMapIndex(k, copyVal)
}
return dst, nil
case reflect.Ptr:
if src.IsNil() {
return src, nil
}
dst := reflect.New(src.Type().Elem())
copyVal, err := deepCopy(src.Elem())
if err != nil {
return src, err
}
dst.Elem().Set(copyVal)
return dst, nil
case reflect.Slice:
if src.IsNil() {
return src, nil
}
dst := reflect.MakeSlice(src.Type(), 0, src.Len())
for i := 0; i < src.Len(); i++ {
copyVal, err := deepCopy(src.Index(i))
if err != nil {
return src, err
}
dst = reflect.Append(dst, copyVal)
}
return dst, nil
case reflect.Struct:
dst := reflect.New(src.Type())
for i := 0; i < src.NumField(); i++ {
if !dst.Elem().Field(i).CanSet() {
// Can't set private fields. At this point, the
// best we can do is a shallow copy. For
// example, time.Time is a value type with
// private members that can be shallow copied.
return src, nil
}
copyVal, err := deepCopy(src.Field(i))
if err != nil {
return src, err
}
dst.Elem().Field(i).Set(copyVal)
}
return dst.Elem(), nil
default:
// Value types like numbers, booleans, and strings.
return src, nil
}
return nil
}

View File

@ -108,6 +108,24 @@ func TestDeepCopyPointerSeparate(t *testing.T) {
}
}
func TestDeepCopyStruct(t *testing.T) {
type Foo struct {
A int
}
type Bar struct {
Foo
F *Foo
}
a := &Bar{Foo{1}, &Foo{2}}
b := copyOrDie(t, a).(*Bar)
a.A = 3
a.F.A = 4
if b.A != 1 || b.F.A != 2 {
t.Errorf("deep copy wasn't deep: %#v, %#v", a, b)
}
}
var result interface{}
func BenchmarkDeepCopy(b *testing.B) {