Ensure empty serialized slices are zero-length, not null

This commit is contained in:
Jordan Liggitt 2017-03-20 23:07:57 -04:00
parent e3f6f14bf0
commit 0e2f1b535d
No known key found for this signature in database
GPG Key ID: 24E7ADF9A3B42012
7 changed files with 41 additions and 15 deletions

View File

@ -30,6 +30,8 @@ import (
"k8s.io/gengo/namer" "k8s.io/gengo/namer"
"k8s.io/gengo/types" "k8s.io/gengo/types"
"reflect"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -722,6 +724,15 @@ func (g *genConversion) doStruct(inType, outType *types.Type, sw *generator.Snip
outMemberType = &copied outMemberType = &copied
} }
// Determine if our destination field is a slice that should be output when empty.
// If it is, ensure a nil source slice converts to a zero-length destination slice.
// See http://issue.k8s.io/43203
persistEmptySlice := false
if outMemberType.Kind == types.Slice {
jsonTag := reflect.StructTag(outMember.Tags).Get("json")
persistEmptySlice = len(jsonTag) > 0 && !strings.Contains(jsonTag, ",omitempty")
}
args := argsFromType(inMemberType, outMemberType).With("name", inMember.Name) args := argsFromType(inMemberType, outMemberType).With("name", inMember.Name)
// try a direct memory copy for any type that has exactly equivalent values // try a direct memory copy for any type that has exactly equivalent values
@ -737,7 +748,15 @@ func (g *genConversion) doStruct(inType, outType *types.Type, sw *generator.Snip
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args) sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args)
continue continue
case types.Slice: case types.Slice:
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args) if persistEmptySlice {
sw.Do("if in.$.name$ == nil {\n", args)
sw.Do("out.$.name$ = make($.outType|raw$, 0)\n", args)
sw.Do("} else {\n", nil)
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args)
sw.Do("}\n", nil)
} else {
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args)
}
continue continue
} }
} }
@ -787,7 +806,11 @@ func (g *genConversion) doStruct(inType, outType *types.Type, sw *generator.Snip
sw.Do("in, out := &in.$.name$, &out.$.name$\n", args) sw.Do("in, out := &in.$.name$, &out.$.name$\n", args)
g.generateFor(inMemberType, outMemberType, sw) g.generateFor(inMemberType, outMemberType, sw)
sw.Do("} else {\n", nil) sw.Do("} else {\n", nil)
sw.Do("out.$.name$ = nil\n", args) if persistEmptySlice {
sw.Do("out.$.name$ = make($.outType|raw$, 0)\n", args)
} else {
sw.Do("out.$.name$ = nil\n", args)
}
sw.Do("}\n", nil) sw.Do("}\n", nil)
case types.Struct: case types.Struct:
if g.isDirectlyAssignable(inMemberType, outMemberType) { if g.isDirectlyAssignable(inMemberType, outMemberType) {

View File

@ -20,6 +20,7 @@ import (
"reflect" "reflect"
"testing" "testing"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
@ -173,7 +174,7 @@ func TestSetDefaultDeployment(t *testing.T) {
t.Errorf("unexpected object: %v", got) t.Errorf("unexpected object: %v", got)
t.FailNow() t.FailNow()
} }
if !reflect.DeepEqual(got.Spec, expected.Spec) { if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) {
t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec) t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec)
} }
} }

View File

@ -20,6 +20,7 @@ import (
"reflect" "reflect"
"testing" "testing"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -156,7 +157,7 @@ func TestSetDefaultDaemonSet(t *testing.T) {
t.Errorf("(%d) unexpected object: %v", i, got) t.Errorf("(%d) unexpected object: %v", i, got)
t.FailNow() t.FailNow()
} }
if !reflect.DeepEqual(got.Spec, expected.Spec) { if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) {
t.Errorf("(%d) got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", i, got.Spec, expected.Spec) t.Errorf("(%d) got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", i, got.Spec, expected.Spec)
} }
} }
@ -295,7 +296,7 @@ func TestSetDefaultDeployment(t *testing.T) {
t.Errorf("unexpected object: %v", got) t.Errorf("unexpected object: %v", got)
t.FailNow() t.FailNow()
} }
if !reflect.DeepEqual(got.Spec, expected.Spec) { if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) {
t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec) t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec)
} }
} }

View File

@ -25,12 +25,12 @@ import (
"os" "os"
"os/user" "os/user"
"path" "path"
"reflect"
"sort" "sort"
"strings" "strings"
"testing" "testing"
"time" "time"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
@ -623,7 +623,7 @@ func TestGetFirstPod(t *testing.T) {
t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, numPods) t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, numPods)
continue continue
} }
if !reflect.DeepEqual(test.expected, pod) { if !apiequality.Semantic.DeepEqual(test.expected, pod) {
t.Errorf("%s:\nexpected pod:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, pod) t.Errorf("%s:\nexpected pod:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, pod)
} }
} }

View File

@ -21,11 +21,11 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"reflect"
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -194,7 +194,7 @@ func TestMerge(t *testing.T) {
if !test.expectErr { if !test.expectErr {
if err != nil { if err != nil {
t.Errorf("testcase[%d], unexpected error: %v", i, err) t.Errorf("testcase[%d], unexpected error: %v", i, err)
} else if !reflect.DeepEqual(out, test.expected) { } else if !apiequality.Semantic.DeepEqual(out, test.expected) {
t.Errorf("\n\ntestcase[%d]\nexpected:\n%+v\nsaw:\n%+v", i, test.expected, out) t.Errorf("\n\ntestcase[%d]\nexpected:\n%+v\nsaw:\n%+v", i, test.expected, out)
} }
} }
@ -374,7 +374,7 @@ func TestMaybeConvert(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(test.expected, obj) { if !apiequality.Semantic.DeepEqual(test.expected, obj) {
t.Errorf("expected:\n%#v\nsaw:\n%#v\n", test.expected, obj) t.Errorf("expected:\n%#v\nsaw:\n%#v\n", test.expected, obj)
} }
} }

View File

@ -556,7 +556,7 @@ func TestResourceByName(t *testing.T) {
if err != nil || !singleItemImplied || len(test.Infos) != 1 { if err != nil || !singleItemImplied || len(test.Infos) != 1 {
t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos) t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
} }
if !reflect.DeepEqual(&pods.Items[0], test.Objects()[0]) { if !apiequality.Semantic.DeepEqual(&pods.Items[0], test.Objects()[0]) {
t.Errorf("unexpected object: %#v", test.Objects()[0]) t.Errorf("unexpected object: %#v", test.Objects()[0])
} }
@ -621,10 +621,10 @@ func TestResourceNames(t *testing.T) {
if err != nil || len(test.Infos) != 2 { if err != nil || len(test.Infos) != 2 {
t.Fatalf("unexpected response: %v %#v", err, test.Infos) t.Fatalf("unexpected response: %v %#v", err, test.Infos)
} }
if !reflect.DeepEqual(&pods.Items[0], test.Objects()[0]) { if !apiequality.Semantic.DeepEqual(&pods.Items[0], test.Objects()[0]) {
t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[0], &pods.Items[0]) t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[0], &pods.Items[0])
} }
if !reflect.DeepEqual(&svc.Items[0], test.Objects()[1]) { if !apiequality.Semantic.DeepEqual(&svc.Items[0], test.Objects()[1]) {
t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[1], &svc.Items[0]) t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[1], &svc.Items[0])
} }
} }
@ -698,7 +698,7 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
if err != nil || !singleItemImplied || len(infos) != 1 { if err != nil || !singleItemImplied || len(infos) != 1 {
t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, infos) t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, infos)
} }
if !reflect.DeepEqual(&pods.Items[0], infos[0].Object) { if !apiequality.Semantic.DeepEqual(&pods.Items[0], infos[0].Object) {
t.Errorf("unexpected object: %#v", infos[0]) t.Errorf("unexpected object: %#v", infos[0])
} }

View File

@ -26,6 +26,7 @@ import (
"testing" "testing"
"time" "time"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -1475,7 +1476,7 @@ func TestUpdateRcWithRetries(t *testing.T) {
updates = updates[1:] updates = updates[1:]
// We should always get an update with a valid rc even when the get fails. The rc should always // We should always get an update with a valid rc even when the get fails. The rc should always
// contain the update. // contain the update.
if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !reflect.DeepEqual(rc, c) { if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !apiequality.Semantic.DeepEqual(rc, c) {
t.Errorf("Unexpected update body, got %+v expected %+v", c, rc) t.Errorf("Unexpected update body, got %+v expected %+v", c, rc)
} else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" { } else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" {
t.Errorf("Expected selector label update, got %+v", c.Spec.Selector) t.Errorf("Expected selector label update, got %+v", c.Spec.Selector)