Skip to content

Commit

Permalink
deepcopy-gen: make deepcopy statically typed and without reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
sttts committed Jul 5, 2017
1 parent 1c55a30 commit a906434
Show file tree
Hide file tree
Showing 9 changed files with 499 additions and 154 deletions.
346 changes: 214 additions & 132 deletions examples/deepcopy-gen/generators/deepcopy.go

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions examples/deepcopy-gen/generators/deepcopy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package generators

import (
"reflect"
"testing"

"k8s.io/gengo/types"
Expand Down Expand Up @@ -342,3 +343,56 @@ func Test_extractTagParams(t *testing.T) {
}
}
}

func Test_extractInterfacesTag(t *testing.T) {
testCases := []struct {
comments []string
expect []string
}{
{
comments: []string{},
expect: nil,
},
{
comments: []string{
"+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
},
},
{
comments: []string{
"+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
"+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.List",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
"k8s.io/kubernetes/runtime.List",
},
},
{
comments: []string{
"+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
"+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
"k8s.io/kubernetes/runtime.Object",
},
},
}

for i, tc := range testCases {
r := extractInterfacesTag(tc.comments)
if r == nil && tc.expect != nil {
t.Errorf("case[%d]: expected non-nil", i)
}
if r != nil && tc.expect == nil {
t.Errorf("case[%d]: expected nil, got %v", i, r)
}
if r != nil && !reflect.DeepEqual(r, tc.expect) {
t.Errorf("case[%d]: expected %v, got %v", i, tc.expect, r)
}
}
}
43 changes: 26 additions & 17 deletions examples/deepcopy-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,44 @@ limitations under the License.

// deepcopy-gen is a tool for auto-generating DeepCopy functions.
//
// Given a list of input directories, it will generate functions that
// efficiently perform a full deep-copy of each type. For any type that
// offers a `.DeepCopy()` method, it will simply call that. Otherwise it will
// use standard value assignment whenever possible. If that is not possible it
// will try to call its own generated copy function for the type, if the type is
// within the allowed root packages. Failing that, it will fall back on
// `conversion.Cloner.DeepCopy(val)` to make the copy. The resulting file will
// be stored in the same directory as the processed source package.
// Given a list of input directories, it will generate DeepCopy and DeepCopyInto
// methods that efficiently perform a full deep-copy of each type. If these
// already exist (are predefined by the developer), they are used instead of
// generating new ones.
//
// Generation is governed by comment tags in the source. Any package may
// If interfaces are referenced in types, it is expected that corresponding
// DeepCopyInterfaceName methods exist, e.g. DeepCopyObject for runtime.Object.
// These can be predefined by the developer or generated through tags, see below.
// They must be added to the interfaces themselves manually, e.g.
// type Object interface {
// ...
// DeepCopyObject() Object
// }
//
// All generation is governed by comment tags in the source. Any package may
// request DeepCopy generation by including a comment in the file-comments of
// one file, of the form:
// // +k8s:deepcopy-gen=package
//
// Packages can request that the generated DeepCopy functions be registered
// with an `init()` function call to `Scheme.AddGeneratedDeepCopyFuncs()` by
// changing the tag to:
// // +k8s:deepcopy-gen=package,register
//
// DeepCopy functions can be generated for individual types, rather than the
// entire package by specifying a comment on the type definion of the form:
// // +k8s:deepcopy-gen=true
//
// When generating for a whole package, individual types may opt out of
// DeepCopy generation by specifying a comment on the of the form:
// DeepCopy generation by specifying a comment on the type definition of the form:
// // +k8s:deepcopy-gen=false
//
// Note that registration is a whole-package option, and is not available for
// individual types.
// Additional DeepCopyInterfaceName methods can be generated by sepcifying a
// comment on the type definition of the form:
// // +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object,k8s.io/kubernetes/runtime.List
// This leads to the generation of DeepCopyObject and DeepCopyList with the given
// interfaces as return types. We say that the tagged type implements deepcopy for the
// interfaces.
//
// The deepcopy funcs for interfaces using "+k8s:deepcopy-gen:interfaces" use the pointer
// of the type as receiver. For those special cases where the non-pointer object should
// implement the interface, this can be done with:
// // +k8s:deepcopy-gen:nonpointer-interfaces=true
package main

import (
Expand Down
5 changes: 3 additions & 2 deletions examples/deepcopy-gen/output_tests/interfaces/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ limitations under the License.
package interfaces

type Inner interface {
function() float64
function() float64
DeepCopyInner() Inner
}

type Ttest struct {
I []Inner
I []Inner
}
25 changes: 25 additions & 0 deletions examples/deepcopy-gen/output_tests/otherpkg/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2017 The Kubernetes 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 otherpkg

type Object interface {
DeepCopyObject() Object
}

type List interface {
DeepCopyList() List
}
41 changes: 41 additions & 0 deletions examples/deepcopy-gen/output_tests/wholepkg/a.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.

package wholepkg

import "k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg"

// Trivial
type Struct_Empty struct{}

Expand Down Expand Up @@ -128,3 +130,42 @@ type Struct_Everything struct {
SliceManualStructField []ManualStruct
ManualSliceField ManualSlice
}

// An Object
// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
type Struct_ExplicitObject struct {
x int
}

// An Object which is used a non-pointer
// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
// +k8s:deepcopy-gen:nonpointer-interfaces=true
type Struct_NonPointerExplicitObject struct {
x int
}

// +k8s:deepcopy-gen=false
type Struct_TypeMeta struct {
}

// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.List
type Struct_ObjectAndList struct {
}

// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
type Struct_ObjectAndObject struct {
}

// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/wholepkg.Selector
// +k8s:deepcopy-gen:interfaces=k8s.io/gengo/examples/deepcopy-gen/output_tests/otherpkg.Object
type Struct_ExplicitSelectorExplicitObject struct {
Struct_TypeMeta
}

type Struct_Interfaces struct {
ObjectField otherpkg.Object
NilObjectField otherpkg.Object
SelectorField Selector
}
103 changes: 100 additions & 3 deletions examples/deepcopy-gen/output_tests/wholepkg/deepcopy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
fuzz "github.com/google/gofuzz"
)

func TestDeepCopy(t *testing.T) {
func TestDeepCopyPrimitives(t *testing.T) {
x := Struct_Primitives{}
y := Struct_Primitives{}

Expand All @@ -39,10 +39,107 @@ func TestDeepCopy(t *testing.T) {
t.Errorf("objects should not be equal, but are")
}

if err := DeepCopy_wholepkg_Struct_Primitives(&x, &y, nil); err != nil {
t.Errorf("unexpected error: %v", err)
x.DeepCopyInto(&y)
if !reflect.DeepEqual(&x, &y) {
t.Errorf("objects should be equal, but are not")
}
}

func TestDeepCopyInterfaceFields(t *testing.T) {
x := Struct_Interfaces{}
y := Struct_Interfaces{}

if !reflect.DeepEqual(&x, &y) {
t.Errorf("objects should be equal to start, but are not")
}

fuzzer := fuzz.New()

obj := Struct_ExplicitObject{}
fuzzer.Fuzz(&obj)
x.ObjectField = &obj

sel := Struct_ExplicitSelectorExplicitObject{}
fuzzer.Fuzz(&sel)
x.SelectorField = &sel

if reflect.DeepEqual(&x, &y) {
t.Errorf("objects should not be equal, but are")
}

x.DeepCopyInto(&y)
if !reflect.DeepEqual(&x, &y) {
t.Errorf("objects should be equal, but are not")
}
}

func TestNilCopy(t *testing.T) {
var x *Struct_B
y := x.DeepCopy()
if y != nil {
t.Error("Expected nil as deepcopy of nil, got %+v", y)
}
}

func assertMethod(t *testing.T, typ reflect.Type, name string) {
if _, found := typ.MethodByName(name); !found {
t.Errorf("Struct_ExplicitObject must have %v method", name)
}
}

func assertNotMethod(t *testing.T, typ reflect.Type, name string) {
if _, found := typ.MethodByName(name); found {
t.Errorf("%v must not have %v method", typ, name)
}
}

func TestInterfaceTypes(t *testing.T) {
explicitObject := reflect.TypeOf(&Struct_ExplicitObject{})
assertMethod(t, explicitObject, "DeepCopyObject")

typeMeta := reflect.TypeOf(&Struct_TypeMeta{})
assertNotMethod(t, typeMeta, "DeepCopy")

objectAndList := reflect.TypeOf(&Struct_ObjectAndList{})
assertMethod(t, objectAndList, "DeepCopyObject")
assertMethod(t, objectAndList, "DeepCopyList")

objectAndObject := reflect.TypeOf(&Struct_ObjectAndObject{})
assertMethod(t, objectAndObject, "DeepCopyObject")

explicitSelectorExplicitObject := reflect.TypeOf(&Struct_ExplicitSelectorExplicitObject{})
assertMethod(t, explicitSelectorExplicitObject, "DeepCopySelector")
assertMethod(t, explicitSelectorExplicitObject, "DeepCopyObject")
}

func TestInterfaceDeepCopy(t *testing.T) {
x := Struct_ExplicitObject{}

fuzzer := fuzz.New()
fuzzer.Fuzz(&x)

y_obj := x.DeepCopyObject()
y, ok := y_obj.(*Struct_ExplicitObject)
if !ok {
t.Fatalf("epxected Struct_ExplicitObject from Struct_ExplicitObject.DeepCopyObject, got: %t", y_obj)
}
if !reflect.DeepEqual(y, &x) {
t.Error("objects should be equal, but are not")
}
}

func TestInterfaceNonPointerDeepCopy(t *testing.T) {
x := Struct_NonPointerExplicitObject{}

fuzzer := fuzz.New()
fuzzer.Fuzz(&x)

y_obj := x.DeepCopyObject()
y, ok := y_obj.(Struct_NonPointerExplicitObject)
if !ok {
t.Fatalf("epxected Struct_NonPointerExplicitObject from Struct_NonPointerExplicitObject.DeepCopyObject, got: %t", y_obj)
}
if !reflect.DeepEqual(y, x) {
t.Error("objects should be equal, but are not")
}
}
21 changes: 21 additions & 0 deletions examples/deepcopy-gen/output_tests/wholepkg/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Copyright 2017 The Kubernetes 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 wholepkg

type Selector interface {
DeepCopySelector() Selector
}
15 changes: 15 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.

package types

import "strings"

// Ref makes a reference to the given type. It can only be used for e.g.
// passing to namers.
func Ref(packageName, typeName string) *Type {
Expand Down Expand Up @@ -44,6 +46,19 @@ func (n Name) String() string {
return n.Package + "." + n.Name
}

// ParseFullyQualifiedName parses a name like k8s.io/kubernetes/pkg/api.Pod into a Name.
func ParseFullyQualifiedName(fqn string) Name {
cs := strings.Split(fqn, ".")
pkg := ""
if len(cs) > 1 {
pkg = strings.Join(cs[0:len(cs) - 1], ".")
}
return Name{
Name: cs[len(cs) - 1],
Package: pkg,
}
}

// The possible classes of types.
type Kind string

Expand Down

0 comments on commit a906434

Please sign in to comment.