diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index d337f831588..3d7a050017c 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -71,7 +71,7 @@ type Converter struct { // NewConverter creates a new Converter object. func NewConverter() *Converter { - return &Converter{ + c := &Converter{ conversionFuncs: map[typePair]reflect.Value{}, defaultingFuncs: map[reflect.Type]reflect.Value{}, nameFunc: func(t reflect.Type) string { return t.Name() }, @@ -81,6 +81,15 @@ func NewConverter() *Converter { inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{}, inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{}, } + c.RegisterConversionFunc(byteSliceCopy) + return c +} + +// Prevent recursing into every byte... +func byteSliceCopy(in *[]byte, out *[]byte, s Scope) error { + *out = make([]byte, len(*in)) + copy(*out, *in) + return nil } // Scope is passed to conversion funcs to allow them to continue an ongoing conversion. diff --git a/pkg/conversion/converter_test.go b/pkg/conversion/converter_test.go index d400da1ecb3..df33d78ee21 100644 --- a/pkg/conversion/converter_test.go +++ b/pkg/conversion/converter_test.go @@ -26,6 +26,19 @@ import ( "github.com/google/gofuzz" ) +func TestConverter_byteSlice(t *testing.T) { + c := NewConverter() + src := []byte{1, 2, 3} + dest := []byte{} + err := c.Convert(&src, &dest, 0, nil) + if err != nil { + t.Fatalf("expected no error") + } + if e, a := src, dest; !reflect.DeepEqual(e, a) { + t.Errorf("expected %#v, got %#v", e, a) + } +} + func TestConverter_DefaultConvert(t *testing.T) { type A struct { Foo string diff --git a/pkg/conversion/deep_copy.go b/pkg/conversion/deep_copy.go new file mode 100644 index 00000000000..ee4be1f6436 --- /dev/null +++ b/pkg/conversion/deep_copy.go @@ -0,0 +1,38 @@ +/* +Copyright 2015 Google Inc. 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 conversion + +import ( + "reflect" +) + +var deepCopier = NewConverter() + +// DeepCopy makes a deep copy of source. Won't work for any private fields! +// For nil slices, will return 0-length slices. These are equivilent in +// basically every way except for the way that reflect.DeepEqual checks. +func DeepCopy(source interface{}) (interface{}, error) { + src := reflect.ValueOf(source) + v := reflect.New(src.Type()).Elem() + s := &scope{ + converter: deepCopier, + } + if err := deepCopier.convert(src, v, s); err != nil { + return nil, err + } + return v.Interface(), nil +} diff --git a/pkg/conversion/deep_copy_test.go b/pkg/conversion/deep_copy_test.go new file mode 100644 index 00000000000..f5d545c6cef --- /dev/null +++ b/pkg/conversion/deep_copy_test.go @@ -0,0 +1,108 @@ +/* +Copyright 2015 Google Inc. 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 conversion + +import ( + "reflect" + "testing" + + "github.com/google/gofuzz" +) + +func TestDeepCopy(t *testing.T) { + semantic := EqualitiesOrDie() + f := fuzz.New().NilChance(.5).NumElements(0, 100) + table := []interface{}{ + map[string]string{}, + int(5), + "hello world", + struct { + A, B, C struct { + D map[string]int + } + X []int + Y []byte + }{}, + } + for _, obj := range table { + obj2, err := DeepCopy(obj) + if err != nil { + t.Errorf("Error: couldn't copy %#v", obj) + continue + } + if e, a := obj, obj2; !semantic.DeepEqual(e, a) { + t.Errorf("expected %#v\ngot %#v", e, a) + } + + obj3 := reflect.New(reflect.TypeOf(obj)).Interface() + f.Fuzz(obj3) + obj4, err := DeepCopy(obj3) + if err != nil { + t.Errorf("Error: couldn't copy %#v", obj) + continue + } + if e, a := obj3, obj4; !semantic.DeepEqual(e, a) { + t.Errorf("expected %#v\ngot %#v", e, a) + } + f.Fuzz(obj3) + } +} + +func copyOrDie(t *testing.T, in interface{}) interface{} { + out, err := DeepCopy(in) + if err != nil { + t.Fatalf("DeepCopy failed: %#q: %v", in, err) + } + return out +} + +func TestDeepCopySliceSeparate(t *testing.T) { + x := []int{5} + y := copyOrDie(t, x).([]int) + x[0] = 3 + if y[0] == 3 { + t.Errorf("deep copy wasn't deep: %#q %#q", x, y) + } +} + +func TestDeepCopyArraySeparate(t *testing.T) { + x := [1]int{5} + y := copyOrDie(t, x).([1]int) + x[0] = 3 + if y[0] == 3 { + t.Errorf("deep copy wasn't deep: %#q %#q", x, y) + } +} + +func TestDeepCopyMapSeparate(t *testing.T) { + x := map[string]int{"foo": 5} + y := copyOrDie(t, x).(map[string]int) + x["foo"] = 3 + if y["foo"] == 3 { + t.Errorf("deep copy wasn't deep: %#q %#q", x, y) + } +} + +func TestDeepCopyPointerSeparate(t *testing.T) { + z := 5 + x := &z + y := copyOrDie(t, x).(*int) + *x = 3 + if *y == 3 { + t.Errorf("deep copy wasn't deep: %#q %#q", x, y) + } +} diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index fcbd0dd06b1..7bdf9df7ab9 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -25,6 +25,7 @@ import ( "strings" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -667,7 +668,7 @@ func (test configCommandTest) run(t *testing.T) string { testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig)) testClearLocationOfOrigin(&actualConfig) - if !reflect.DeepEqual(test.expectedConfig, actualConfig) { + if !api.Semantic.DeepEqual(test.expectedConfig, actualConfig) { t.Errorf("diff: %v", util.ObjectDiff(test.expectedConfig, actualConfig)) t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig) }