Skip to content

Commit f7567fc

Browse files
committed
Add tests for CBOR encoder handling of duplicate field names/tags.
1 parent fc4613f commit f7567fc

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package modes_test
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/fxamacker/cbor/v2"
24+
"github.com/google/go-cmp/cmp"
25+
)
26+
27+
func TestEncode(t *testing.T) {
28+
for _, tc := range []struct {
29+
name string
30+
modes []cbor.EncMode
31+
in interface{}
32+
want []byte
33+
assertOnError func(t *testing.T, e error)
34+
}{
35+
{
36+
name: "all duplicate fields are ignored", // Matches behavior of JSON serializer.
37+
in: struct {
38+
A1 int `json:"a"`
39+
A2 int `json:"a"` //nolint:govet // This is intentional to test that the encoder will not encode two map entries with the same key.
40+
}{},
41+
want: []byte{0xa0}, // {}
42+
assertOnError: assertNilError,
43+
},
44+
{
45+
name: "only tagged field is considered if any are tagged", // Matches behavior of JSON serializer.
46+
in: struct {
47+
A int
48+
TaggedA int `json:"A"`
49+
}{
50+
A: 1,
51+
TaggedA: 2,
52+
},
53+
want: []byte{0xa1, 0x41, 0x41, 0x02}, // {"A": 2}
54+
assertOnError: assertNilError,
55+
},
56+
} {
57+
encModes := tc.modes
58+
if len(encModes) == 0 {
59+
encModes = allEncModes
60+
}
61+
62+
for _, encMode := range encModes {
63+
modeName, ok := encModeNames[encMode]
64+
if !ok {
65+
t.Fatal("test case configured to run against unrecognized mode")
66+
}
67+
68+
t.Run(fmt.Sprintf("mode=%s/%s", modeName, tc.name), func(t *testing.T) {
69+
out, err := encMode.Marshal(tc.in)
70+
tc.assertOnError(t, err)
71+
if diff := cmp.Diff(tc.want, out); diff != "" {
72+
t.Errorf("unexpected output:\n%s", diff)
73+
}
74+
})
75+
}
76+
}
77+
}

staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package json_test
1818

1919
import (
20+
"bytes"
2021
"fmt"
2122
"reflect"
2223
"strings"
@@ -29,6 +30,8 @@ import (
2930
"k8s.io/apimachinery/pkg/runtime/serializer/json"
3031
runtimetesting "k8s.io/apimachinery/pkg/runtime/testing"
3132
"k8s.io/apimachinery/pkg/util/diff"
33+
34+
"github.com/google/go-cmp/cmp"
3235
)
3336

3437
type testDecodable struct {
@@ -933,3 +936,68 @@ func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind,
933936
func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
934937
return false
935938
}
939+
940+
type testEncodableDuplicateTag struct {
941+
metav1.TypeMeta `json:",inline"`
942+
943+
A1 int `json:"a"`
944+
A2 int `json:"a"` //nolint:govet // This is intentional to test that the encoder will not encode two map entries with the same key.
945+
}
946+
947+
func (testEncodableDuplicateTag) DeepCopyObject() runtime.Object {
948+
panic("unimplemented")
949+
}
950+
951+
type testEncodableTagMatchesUntaggedName struct {
952+
metav1.TypeMeta `json:",inline"`
953+
954+
A int
955+
TaggedA int `json:"A"`
956+
}
957+
958+
func (testEncodableTagMatchesUntaggedName) DeepCopyObject() runtime.Object {
959+
panic("unimplemented")
960+
}
961+
962+
func TestEncode(t *testing.T) {
963+
for _, tc := range []struct {
964+
name string
965+
in runtime.Object
966+
want []byte
967+
}{
968+
// The Go visibility rules for struct fields are amended for JSON when deciding
969+
// which field to marshal or unmarshal. If there are multiple fields at the same
970+
// level, and that level is the least nested (and would therefore be the nesting
971+
// level selected by the usual Go rules), the following extra rules apply:
972+
973+
// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
974+
// even if there are multiple untagged fields that would otherwise conflict.
975+
{
976+
name: "only tagged field is considered if any are tagged",
977+
in: &testEncodableTagMatchesUntaggedName{
978+
A: 1,
979+
TaggedA: 2,
980+
},
981+
want: []byte("{\"A\":2}\n"),
982+
},
983+
// 2) If there is exactly one field (tagged or not according to the first rule),
984+
// that is selected.
985+
// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
986+
{
987+
name: "all duplicate fields are ignored",
988+
in: &testEncodableDuplicateTag{},
989+
want: []byte("{}\n"),
990+
},
991+
} {
992+
t.Run(tc.name, func(t *testing.T) {
993+
var dst bytes.Buffer
994+
s := json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{})
995+
if err := s.Encode(tc.in, &dst); err != nil {
996+
t.Errorf("unexpected error: %v", err)
997+
}
998+
if diff := cmp.Diff(tc.want, dst.Bytes()); diff != "" {
999+
t.Errorf("unexpected output:\n%s", diff)
1000+
}
1001+
})
1002+
}
1003+
}

0 commit comments

Comments
 (0)