mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #125068 from benluddy/cbor-decode-to-any
KEP-4222: Add unit tests for decoding CBOR into interface{} type
This commit is contained in:
commit
06ba3d8ab6
@ -19,6 +19,7 @@ package modes_test
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@ -44,6 +45,11 @@ func TestDecode(t *testing.T) {
|
||||
into interface{} // prototype for concrete destination type. if nil, decode into empty interface value.
|
||||
want interface{}
|
||||
assertOnError func(t *testing.T, e error)
|
||||
|
||||
// TODO: Some failing test cases are included for completeness. The next library
|
||||
// minor version should allow them all to be fixed. In the meantime, this field
|
||||
// explains the behavior reason for a particular failure.
|
||||
fixme string
|
||||
}
|
||||
|
||||
// Test cases are grouped by the kind of the CBOR data item being decoded, as enumerated in
|
||||
@ -64,6 +70,13 @@ func TestDecode(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("%s/mode=%s", test.name, modeName), func(t *testing.T) {
|
||||
if test.fixme != "" {
|
||||
// TODO: Remove this along with the
|
||||
// fixme field when the last skipped
|
||||
// test case is passing.
|
||||
t.Skip(test.fixme)
|
||||
}
|
||||
|
||||
var dst reflect.Value
|
||||
if test.into == nil {
|
||||
var i interface{}
|
||||
@ -73,7 +86,7 @@ func TestDecode(t *testing.T) {
|
||||
}
|
||||
err := decMode.Unmarshal(test.in, dst.Interface())
|
||||
test.assertOnError(t, err)
|
||||
if test.want != nil {
|
||||
if err == nil {
|
||||
if diff := cmp.Diff(test.want, dst.Elem().Interface()); diff != "" {
|
||||
t.Errorf("unexpected output:\n%s", diff)
|
||||
}
|
||||
@ -92,11 +105,65 @@ func TestDecode(t *testing.T) {
|
||||
want: int64(10),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "int64 minimum positive value",
|
||||
in: hex("00"), // 0
|
||||
want: int64(0),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "int64 max positive value",
|
||||
in: hex("1b7fffffffffffffff"), // 9223372036854775807
|
||||
want: int64(9223372036854775807),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "max positive integer value supported by cbor: 2^64 - 1",
|
||||
in: hex("1bffffffffffffffff"), // 18446744073709551615
|
||||
assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) {
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
} else if want := "cbor: cannot unmarshal positive integer into Go value of type int64 (18446744073709551615 overflows Go's int64)"; want != e.Error() {
|
||||
t.Errorf("want error %q, got %q", want, e.Error())
|
||||
}
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "negative integer", []test{})
|
||||
group(t, "negative integer", []test{
|
||||
{
|
||||
name: "int64 max negative value",
|
||||
in: hex("20"), // -1
|
||||
want: int64(-1),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "int64 min negative value",
|
||||
in: hex("3b7fffffffffffffff"), // -9223372036854775808
|
||||
want: int64(-9223372036854775808),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "min negative integer value supported by cbor: -2^64",
|
||||
in: hex("3bffffffffffffffff"), // -18446744073709551616
|
||||
assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) {
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
} else if want := "cbor: cannot unmarshal negative integer into Go value of type int64 (-18446744073709551616 overflows Go's int64)"; want != e.Error() {
|
||||
t.Errorf("want error %q, got %q", want, e.Error())
|
||||
}
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "byte string", []test{})
|
||||
group(t, "byte string", []test{
|
||||
{
|
||||
name: "empty byte string",
|
||||
in: hex("40"), // ''
|
||||
want: "",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "text string", []test{
|
||||
{
|
||||
@ -115,6 +182,12 @@ func TestDecode(t *testing.T) {
|
||||
want: "abc",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "empty text string",
|
||||
in: hex("60"), // ""
|
||||
want: "",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "array", []test{
|
||||
@ -329,21 +402,395 @@ func TestDecode(t *testing.T) {
|
||||
},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "map with non-string key types",
|
||||
in: hex("a1fb40091eb851eb851f63706965"), // {3.14: "pie"}
|
||||
assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) {
|
||||
if e.CBORType != "primitives" || e.GoType != "string" {
|
||||
t.Errorf("expected %q, got %q", &cbor.UnmarshalTypeError{CBORType: "primitives", GoType: "string"}, e)
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "map with byte string key",
|
||||
in: hex("a143abcdef187b"), // {h'abcdef': 123}
|
||||
want: map[string]interface{}{"\xab\xcd\xef": int64(123)},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "map with text string key",
|
||||
in: hex("a143414243187b"), // {"ABC": 123}
|
||||
want: map[string]interface{}{"ABC": int64(123)},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "map with mixed string key types",
|
||||
in: hex("a243abcdef187b43414243187c"), // {h'abcdef': 123, "ABC": 124}
|
||||
want: map[string]interface{}{"\xab\xcd\xef": int64(123), "ABC": int64(124)},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "floating-point number", []test{})
|
||||
group(t, "floating-point number", []test{
|
||||
{
|
||||
name: "half precision infinity",
|
||||
in: hex("f97c00"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "single precision infinity",
|
||||
in: hex("fa7f800000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "double precision infinity",
|
||||
in: hex("fb7ff0000000000000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "half precision negative infinity",
|
||||
in: hex("f9fc00"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "single precision negative infinity",
|
||||
in: hex("faff800000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "double precision negative infinity",
|
||||
in: hex("fbfff0000000000000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "half precision NaN",
|
||||
in: hex("f97e00"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "single precision NaN",
|
||||
in: hex("fa7fc00000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "double precision NaN",
|
||||
in: hex("fb7ff8000000000000"),
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
if e == nil {
|
||||
t.Fatal("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "NaN and positive/negative infinities should be rejected",
|
||||
},
|
||||
{
|
||||
name: "smallest nonzero float64",
|
||||
in: hex("fb0000000000000001"),
|
||||
want: float64(math.SmallestNonzeroFloat64),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "max float64 value",
|
||||
in: hex("fb7fefffffffffffff"),
|
||||
want: float64(math.MaxFloat64),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "max float32 value as double precision",
|
||||
in: hex("fb47efffffe0000000"),
|
||||
want: float64(math.MaxFloat32),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "max float32 value as single precision",
|
||||
in: hex("fa7f7fffff"),
|
||||
want: float64(math.MaxFloat32),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "half precision",
|
||||
in: hex("f94200"),
|
||||
want: float64(3),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "double precision without fractional component",
|
||||
in: hex("fb4000000000000000"),
|
||||
want: float64(2),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "single precision without fractional component",
|
||||
in: hex("fa40000000"),
|
||||
want: float64(2),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "half precision without fractional component",
|
||||
in: hex("f94000"),
|
||||
want: float64(2),
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "simple value", []test{})
|
||||
group(t, "simple value", append([]test{
|
||||
{
|
||||
name: "simple value 20",
|
||||
in: hex("f4"), // false
|
||||
want: false,
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "simple value 21",
|
||||
in: hex("f5"), // true
|
||||
want: true,
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "simple value 22",
|
||||
in: hex("f6"), // nil
|
||||
want: nil,
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "simple value 23",
|
||||
in: hex("f7"), // undefined
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
// TODO: Once this can pass, make the assertion stronger.
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
}
|
||||
},
|
||||
fixme: "cbor simple value 23 (\"undefined\") should not be accepted",
|
||||
},
|
||||
}, func() (generated []test) {
|
||||
// Generate test cases for all simple values (0 to 255) because the number of possible simple values is fixed and small.
|
||||
for i := 0; i <= 255; i++ {
|
||||
each := test{
|
||||
name: fmt.Sprintf("simple value %d", i),
|
||||
}
|
||||
if i <= 23 {
|
||||
each.in = []byte{byte(0xe0) | byte(i)}
|
||||
} else {
|
||||
// larger simple values encode to two bytes
|
||||
each.in = []byte{byte(0xe0) | byte(24), byte(i)}
|
||||
}
|
||||
switch i {
|
||||
case 20, 21, 22, 23: // recognized values with explicit cases
|
||||
continue
|
||||
case 24, 25, 26, 27, 28, 29, 30, 31: // reserved
|
||||
// these are considered not well-formed
|
||||
each.assertOnError = assertOnConcreteError(func(t *testing.T, e *cbor.SyntaxError) {
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
} else if want := fmt.Sprintf("cbor: invalid simple value %d for type primitives", i); want != e.Error() {
|
||||
t.Errorf("want error %q, got %q", want, e.Error())
|
||||
}
|
||||
})
|
||||
default:
|
||||
// reject all unrecognized simple values
|
||||
each.assertOnError = func(t *testing.T, e error) {
|
||||
// TODO: Once this can pass, make the assertion stronger.
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
}
|
||||
}
|
||||
each.fixme = "unrecognized simple values should be rejected"
|
||||
}
|
||||
generated = append(generated, each)
|
||||
}
|
||||
return
|
||||
}()...))
|
||||
|
||||
t.Run("tag", func(t *testing.T) {
|
||||
group(t, "rfc3339 time", []test{})
|
||||
group(t, "rfc3339 time", []test{
|
||||
{
|
||||
name: "tag 0 RFC3339 text string",
|
||||
in: hex("c074323030362d30312d30325431353a30343a30355a"), // 0("2006-01-02T15:04:05Z")
|
||||
want: "2006-01-02T15:04:05Z",
|
||||
fixme: "decoding RFC3339 text string tagged with 0 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 0 byte string",
|
||||
in: hex("c054323030362d30312d30325431353a30343a30355a"), // 0('2006-01-02T15:04:05Z')
|
||||
want: "2006-01-02T15:04:05Z",
|
||||
assertOnError: assertErrorMessage("cbor: tag number 0 must be followed by text string, got byte string"),
|
||||
},
|
||||
{
|
||||
name: "tag 0 non-RFC3339 text string",
|
||||
in: hex("c06474657874"), // 0("text")
|
||||
assertOnError: assertErrorMessage(`cbor: cannot set text for time.Time: parsing time "text" as "2006-01-02T15:04:05Z07:00": cannot parse "text" as "2006"`),
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "epoch time", []test{})
|
||||
group(t, "epoch time", []test{
|
||||
{
|
||||
name: "tag 1 timestamp unsigned integer",
|
||||
in: hex("c11a43b940e5"), // 1(1136214245)
|
||||
want: "2006-01-02T15:04:05Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with float16 value",
|
||||
in: hex("c1f93c00"), // 1(1.0_1)
|
||||
want: "1970-01-01T00:00:01Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with float32 value",
|
||||
in: hex("c1fa3f800000"), // 1(1.0_2)
|
||||
want: "1970-01-01T00:00:01Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with float64 value",
|
||||
in: hex("c1fb3ff0000000000000"), // 1(1.0_3)
|
||||
want: "1970-01-01T00:00:01Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a five digit year",
|
||||
in: hex("c11b0000003afff44181"), // 1(253402300801)
|
||||
want: "10000-01-01T00:00:01Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a negative integer value",
|
||||
in: hex("c120"), // 1(-1)
|
||||
want: "1969-12-31T23:59:59Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a negative float16 value",
|
||||
in: hex("c1f9bc00"), // 1(-1.0_1)
|
||||
want: "1969-12-31T23:59:59Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a negative float32 value",
|
||||
in: hex("c1fabf800000"), // 1(-1.0_2)
|
||||
want: "1969-12-31T23:59:59Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a negative float64 value",
|
||||
in: hex("c1fbbff0000000000000"), // 1(-1.0_3)
|
||||
want: "1969-12-31T23:59:59Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a positive infinity",
|
||||
in: hex("c1f97c00"), // 1(Infinity)
|
||||
want: "0001-01-01T00:00:00Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with a negative infinity",
|
||||
in: hex("c1f9fc00"), // 1(-Infinity)
|
||||
want: "0001-01-01T00:00:00Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "tag 1 with NaN",
|
||||
in: hex("c1f9fc00"), // 1(NaN)
|
||||
want: "0001-01-01T00:00:00Z",
|
||||
fixme: "decoding cbor data tagged with 1 produces time.Time instead of RFC3339 timestamp string",
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "unsigned bignum", []test{})
|
||||
group(t, "unsigned bignum", []test{
|
||||
{
|
||||
name: "rejected",
|
||||
in: hex("c249010000000000000000"), // 2(18446744073709551616)
|
||||
fixme: "decoding cbor data tagged with 2 produces big.Int instead of rejecting",
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
// TODO: Once this can pass, make the assertion stronger.
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "negative bignum", []test{})
|
||||
group(t, "negative bignum", []test{
|
||||
{
|
||||
name: "rejected",
|
||||
in: hex("c349010000000000000000"), // 3(-18446744073709551617)
|
||||
fixme: "decoding cbor data tagged with 3 produces big.Int instead of rejecting",
|
||||
assertOnError: func(t *testing.T, e error) {
|
||||
// TODO: Once this can pass, make the assertion stronger.
|
||||
if e == nil {
|
||||
t.Error("expected non-nil error")
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
group(t, "unrecognized", []test{})
|
||||
group(t, "unrecognized", []test{
|
||||
{
|
||||
name: "decimal fraction",
|
||||
in: hex("c48221196ab3"), // 4([-2, 27315])
|
||||
want: []interface{}{int64(-2), int64(27315)},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
{
|
||||
name: "bigfloat",
|
||||
in: hex("c5822003"), // 5([-1, 3])
|
||||
want: []interface{}{int64(-1), int64(3)},
|
||||
assertOnError: assertNilError,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -62,6 +62,18 @@ func assertOnConcreteError[E error](fn func(*testing.T, E)) func(t *testing.T, e
|
||||
}
|
||||
}
|
||||
|
||||
func assertErrorMessage(want string) func(*testing.T, error) {
|
||||
return func(t *testing.T, got error) {
|
||||
if got == nil {
|
||||
t.Error("expected non-nil error")
|
||||
return
|
||||
}
|
||||
if got.Error() != want {
|
||||
t.Errorf("got error %q, want %q", got.Error(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertIdenticalError[E error](expected E) func(*testing.T, error) {
|
||||
return assertOnConcreteError(func(t *testing.T, actual E) {
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
|
Loading…
Reference in New Issue
Block a user