diff --git a/pkg/api/copy_test.go b/pkg/api/copy_test.go new file mode 100644 index 00000000000..08e4a7ab5ef --- /dev/null +++ b/pkg/api/copy_test.go @@ -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) + } + } + } + } +} diff --git a/pkg/conversion/deep_copy.go b/pkg/conversion/deep_copy.go index 04b3a8be58f..4927ecfba88 100644 --- a/pkg/conversion/deep_copy.go +++ b/pkg/conversion/deep_copy.go @@ -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 - } - - return v.Elem().Interface(), nil + v, err := deepCopy(reflect.ValueOf(source)) + return v.Interface(), err } -// 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 -} +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()) -func newGobCopier() gobCopier { - buf := &bytes.Buffer{} - return gobCopier{ - enc: gob.NewEncoder(buf), - dec: gob.NewDecoder(buf), - } -} + 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 (c *gobCopier) CopyInto(dst, src interface{}) error { - if err := c.enc.Encode(src); err != nil { - return err + case reflect.Interface: + if src.IsNil() { + return src, nil + } + return deepCopy(src.Elem()) + + case reflect.Map: + if src.IsNil() { + return src, nil + } + 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 } - if err := c.dec.Decode(dst); err != nil { - return err - } - return nil } diff --git a/pkg/conversion/deep_copy_test.go b/pkg/conversion/deep_copy_test.go index f0d43017e10..9c4d78d0a3b 100644 --- a/pkg/conversion/deep_copy_test.go +++ b/pkg/conversion/deep_copy_test.go @@ -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) {