mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Add decode and roundtrip tests for CBOR marshaling.
Co-authored-by: Suriyan Subbarayan <suriyansub710@gmail.com>
This commit is contained in:
parent
50b4cfc414
commit
57fc5d2401
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 modes_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
hex := func(h string) []byte {
|
||||||
|
b, err := hex.DecodeString(h)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
modes []cbor.DecMode
|
||||||
|
in []byte
|
||||||
|
into interface{} // prototype for concrete destination type. if nil, decode into empty interface value.
|
||||||
|
want interface{}
|
||||||
|
assertOnError func(t *testing.T, e error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reject text string containing invalid utf-8 sequence",
|
||||||
|
in: hex("6180"), // text string beginning with continuation byte 0x80
|
||||||
|
assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.SemanticError) {
|
||||||
|
const expected = "cbor: invalid UTF-8 string"
|
||||||
|
if msg := e.Error(); msg != expected {
|
||||||
|
t.Errorf("expected %v, got %v", expected, msg)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsigned integer decodes to interface{} as int64",
|
||||||
|
in: hex("0a"), // 10
|
||||||
|
want: int64(10),
|
||||||
|
assertOnError: assertNilError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown field error",
|
||||||
|
modes: []cbor.DecMode{modes.Decode},
|
||||||
|
in: hex("a1616101"), // {"a": 1}
|
||||||
|
into: struct{}{},
|
||||||
|
assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnknownFieldError) {
|
||||||
|
if e.Index != 0 {
|
||||||
|
t.Errorf("expected %#v, got %#v", &cbor.UnknownFieldError{Index: 0}, e)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no unknown field error in lax mode",
|
||||||
|
modes: []cbor.DecMode{modes.DecodeLax},
|
||||||
|
in: hex("a1616101"), // {"a": 1}
|
||||||
|
into: struct{}{},
|
||||||
|
want: struct{}{},
|
||||||
|
assertOnError: assertNilError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indefinite-length text string",
|
||||||
|
in: hex("7f616161626163ff"), // (_ "a", "b", "c")
|
||||||
|
want: "abc",
|
||||||
|
assertOnError: assertNilError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested indefinite-length array",
|
||||||
|
in: hex("9f9f8080ff9f8080ffff"), // [_ [_ [] []] [_ [][]]]
|
||||||
|
want: []interface{}{
|
||||||
|
[]interface{}{[]interface{}{}, []interface{}{}},
|
||||||
|
[]interface{}{[]interface{}{}, []interface{}{}},
|
||||||
|
},
|
||||||
|
assertOnError: assertNilError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested indefinite-length map",
|
||||||
|
in: hex("bf6141bf616101616202ff6142bf616901616a02ffff"), // {_ "A": {_ "a": 1, "b": 2}, "B": {_ "i": 1, "j": 2}}
|
||||||
|
want: map[string]interface{}{
|
||||||
|
"A": map[string]interface{}{"a": int64(1), "b": int64(2)},
|
||||||
|
"B": map[string]interface{}{"i": int64(1), "j": int64(2)},
|
||||||
|
},
|
||||||
|
assertOnError: assertNilError,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
ms := tc.modes
|
||||||
|
if len(ms) == 0 {
|
||||||
|
ms = allDecModes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dm := range ms {
|
||||||
|
modeName, ok := decModeNames[dm]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("test case configured to run against unrecognized mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("mode=%s/%s", modeName, tc.name), func(t *testing.T) {
|
||||||
|
var dst reflect.Value
|
||||||
|
if tc.into == nil {
|
||||||
|
var i interface{}
|
||||||
|
dst = reflect.ValueOf(&i)
|
||||||
|
} else {
|
||||||
|
dst = reflect.New(reflect.TypeOf(tc.into))
|
||||||
|
}
|
||||||
|
err := dm.Unmarshal(tc.in, dst.Interface())
|
||||||
|
tc.assertOnError(t, err)
|
||||||
|
if tc.want != nil {
|
||||||
|
if diff := cmp.Diff(tc.want, dst.Elem().Interface()); diff != "" {
|
||||||
|
t.Errorf("unexpected output:\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 modes_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var encModeNames = map[cbor.EncMode]string{
|
||||||
|
modes.Encode: "Encode",
|
||||||
|
modes.EncodeNondeterministic: "EncodeNondeterministic",
|
||||||
|
}
|
||||||
|
|
||||||
|
var allEncModes = []cbor.EncMode{
|
||||||
|
modes.Encode,
|
||||||
|
modes.EncodeNondeterministic,
|
||||||
|
}
|
||||||
|
|
||||||
|
var decModeNames = map[cbor.DecMode]string{
|
||||||
|
modes.Decode: "Decode",
|
||||||
|
modes.DecodeLax: "DecodeLax",
|
||||||
|
}
|
||||||
|
|
||||||
|
var allDecModes = []cbor.DecMode{
|
||||||
|
modes.Decode,
|
||||||
|
modes.DecodeLax,
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNilError(t *testing.T, e error) {
|
||||||
|
if e != nil {
|
||||||
|
t.Errorf("expected nil error, got: %v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertOnConcreteError[E error](fn func(*testing.T, E)) func(t *testing.T, e error) {
|
||||||
|
return func(t *testing.T, ei error) {
|
||||||
|
var ec E
|
||||||
|
if !errors.As(ei, &ec) {
|
||||||
|
t.Errorf("expected concrete error type %T, got %T: %v", ec, ei, ei)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(t, ec)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 modes_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nilPointerFor[T interface{}]() *T {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundtrip(t *testing.T) {
|
||||||
|
type modePair struct {
|
||||||
|
enc cbor.EncMode
|
||||||
|
dec cbor.DecMode
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
modePairs []modePair
|
||||||
|
obj interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil slice",
|
||||||
|
obj: []interface{}(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil map",
|
||||||
|
obj: map[string]interface{}(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty slice",
|
||||||
|
obj: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty map",
|
||||||
|
obj: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to slice",
|
||||||
|
obj: nilPointerFor[[]interface{}](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to map",
|
||||||
|
obj: nilPointerFor[map[string]interface{}](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nonempty string",
|
||||||
|
obj: "hello world",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
obj: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string containing invalid UTF-8 sequence",
|
||||||
|
obj: "\x80", // first byte is a continuation byte
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true",
|
||||||
|
obj: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false",
|
||||||
|
obj: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64",
|
||||||
|
obj: int64(5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 max",
|
||||||
|
obj: int64(math.MaxInt64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 min",
|
||||||
|
obj: int64(math.MinInt64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 zero",
|
||||||
|
obj: int64(math.MinInt64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64 max",
|
||||||
|
obj: uint64(math.MaxUint64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64 zero",
|
||||||
|
obj: uint64(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 max",
|
||||||
|
obj: int32(math.MaxInt32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 min",
|
||||||
|
obj: int32(math.MinInt32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 zero",
|
||||||
|
obj: int32(math.MinInt32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint32 max",
|
||||||
|
obj: uint32(math.MaxUint32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint32 zero",
|
||||||
|
obj: uint32(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int16 max",
|
||||||
|
obj: int16(math.MaxInt16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int16 min",
|
||||||
|
obj: int16(math.MinInt16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int16 zero",
|
||||||
|
obj: int16(math.MinInt16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint16 max",
|
||||||
|
obj: uint16(math.MaxUint16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint16 zero",
|
||||||
|
obj: uint16(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int8 max",
|
||||||
|
obj: int8(math.MaxInt8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int8 min",
|
||||||
|
obj: int8(math.MinInt8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int8 zero",
|
||||||
|
obj: int8(math.MinInt8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint8 max",
|
||||||
|
obj: uint8(math.MaxUint8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint8 zero",
|
||||||
|
obj: uint8(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64",
|
||||||
|
obj: float64(2.71),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 max",
|
||||||
|
obj: float64(math.MaxFloat64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 smallest nonzero",
|
||||||
|
obj: float64(math.SmallestNonzeroFloat64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 no fractional component",
|
||||||
|
obj: float64(5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32",
|
||||||
|
obj: float32(2.71),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 max",
|
||||||
|
obj: float32(math.MaxFloat32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 smallest nonzero",
|
||||||
|
obj: float32(math.SmallestNonzeroFloat32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 no fractional component",
|
||||||
|
obj: float32(5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "time.Time",
|
||||||
|
obj: time.Date(2222, time.May, 4, 12, 13, 14, 123, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V int64 `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V float64 `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V string `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V bool `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V *struct{} `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to slice as struct field",
|
||||||
|
obj: struct {
|
||||||
|
V *[]interface{} `json:"v"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to slice as struct field with omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V *[]interface{} `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to map as struct field",
|
||||||
|
obj: struct {
|
||||||
|
V *map[string]interface{} `json:"v"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer to map as struct field with omitempty",
|
||||||
|
obj: struct {
|
||||||
|
V *map[string]interface{} `json:"v,omitempty"`
|
||||||
|
}{},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
mps := tc.modePairs
|
||||||
|
if len(mps) == 0 {
|
||||||
|
// Default is all modes to all modes.
|
||||||
|
mps = []modePair{}
|
||||||
|
for _, em := range allEncModes {
|
||||||
|
for _, dm := range allDecModes {
|
||||||
|
mps = append(mps, modePair{enc: em, dec: dm})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mp := range mps {
|
||||||
|
encModeName, ok := encModeNames[mp.enc]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("test case configured to run against unrecognized encode mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
decModeName, ok := decModeNames[mp.dec]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("test case configured to run against unrecognized decode mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("enc=%s/dec=%s/%s", encModeName, decModeName, tc.name), func(t *testing.T) {
|
||||||
|
original := tc.obj
|
||||||
|
|
||||||
|
b, err := mp.enc.Marshal(original)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from Marshal: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
final := reflect.New(reflect.TypeOf(original))
|
||||||
|
err = mp.dec.Unmarshal(b, final.Interface())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(original, final.Elem().Interface()) {
|
||||||
|
t.Errorf("roundtrip difference:\nwant: %#v\ngot: %#v", original, final)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user