mirror of https://github.com/knative/pkg.git
469 lines
10 KiB
Go
469 lines
10 KiB
Go
/*
|
|
Copyright 2017 The Knative 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 duck
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
)
|
|
|
|
func TestMatches(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
instance interface{}
|
|
iface Implementable
|
|
}{{
|
|
name: "foo matches fooable",
|
|
instance: &Foo{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "bar matches barable",
|
|
instance: &Bar{},
|
|
iface: &Barable{},
|
|
}, {
|
|
name: "slice matches sliceable",
|
|
instance: &Slice{},
|
|
iface: &Sliceable{},
|
|
}, {
|
|
name: "string matches stringable",
|
|
instance: &String{},
|
|
iface: &emptyStringable,
|
|
}, {
|
|
name: "other matches foo",
|
|
instance: &struct {
|
|
Status struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "other (all) matches fooable",
|
|
instance: &struct {
|
|
Status struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
Barable *Barable `json:"barable,omitempty"`
|
|
Sliceable Sliceable `json:"sliceable,omitempty"`
|
|
Stringable *Stringable `json:"stringable,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "other (all) matches barable",
|
|
instance: &struct {
|
|
Status struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
Barable *Barable `json:"barable,omitempty"`
|
|
Sliceable Sliceable `json:"sliceable,omitempty"`
|
|
Stringable *Stringable `json:"stringable,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Barable{},
|
|
}, {
|
|
name: "other (all) matches sliceable",
|
|
instance: &struct {
|
|
Status struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
Barable *Barable `json:"barable,omitempty"`
|
|
Sliceable Sliceable `json:"sliceable,omitempty"`
|
|
Stringable *Stringable `json:"stringable,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Sliceable{},
|
|
}, {
|
|
name: "other (all) matches stringable",
|
|
instance: &struct {
|
|
Status struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
Barable *Barable `json:"barable,omitempty"`
|
|
Sliceable Sliceable `json:"sliceable,omitempty"`
|
|
Stringable *Stringable `json:"stringable,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &emptyStringable,
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := VerifyType(test.instance, test.iface); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
ok, err := ConformsToType(test.instance, test.iface)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if !ok {
|
|
t.Errorf("Expected %T to conform to %T", test.instance, test.iface)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMismatches(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
instance interface{}
|
|
iface Implementable
|
|
}{{
|
|
name: "foo doesn't match barable",
|
|
instance: &Foo{},
|
|
iface: &Barable{},
|
|
}, {
|
|
name: "bar doesn't match fooable",
|
|
instance: &Bar{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "foo doesn't match sliceable",
|
|
instance: &Foo{},
|
|
iface: &Sliceable{},
|
|
}, {
|
|
name: "other matches neither (foo)",
|
|
instance: &struct {
|
|
Status struct {
|
|
Done bool `json:"done,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "other matches neither (slice)",
|
|
instance: &struct {
|
|
Status struct {
|
|
Done bool `json:"done,omitempty"`
|
|
} `json:"status,omitempty"`
|
|
}{},
|
|
iface: &Sliceable{},
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := VerifyType(test.instance, test.iface); err == nil {
|
|
t.Errorf("Unexpected success %T implements %T", test.instance, test.iface)
|
|
}
|
|
|
|
ok, err := ConformsToType(test.instance, test.iface)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if ok {
|
|
t.Errorf("Expected %T to not conform to %T", test.instance, test.iface)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
instance interface{}
|
|
iface Implementable
|
|
}{{
|
|
name: "duck type - fails to marshal",
|
|
instance: &Foo{},
|
|
iface: &UnableToMarshal{},
|
|
}, {
|
|
name: "duck type - fails to unmarshal",
|
|
instance: &Foo{},
|
|
iface: &UnableToUnmarshal{},
|
|
}, {
|
|
name: "instance - fails to unmarshal",
|
|
instance: &UnableToUnmarshal{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "instance - fails to marshal",
|
|
instance: &UnableToMarshal{},
|
|
iface: &Fooable{},
|
|
}, {
|
|
name: "duck type - unexported fields",
|
|
instance: &Foo{},
|
|
iface: &UnexportedFields{},
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := VerifyType(test.instance, test.iface); err == nil {
|
|
t.Error("expected VerifyType to return an error")
|
|
}
|
|
|
|
if _, err := ConformsToType(test.instance, test.iface); err == nil {
|
|
t.Error("expected ConformsToType to return an error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Define a "Fooable" duck type.
|
|
type Fooable struct {
|
|
Field1 string `json:"field1,omitempty"`
|
|
Field2 string `json:"field2,omitempty"`
|
|
}
|
|
type Foo struct {
|
|
Status FooStatus `json:"status"`
|
|
}
|
|
type FooStatus struct {
|
|
Fooable *Fooable `json:"fooable,omitempty"`
|
|
}
|
|
|
|
var (
|
|
_ Implementable = (*Fooable)(nil)
|
|
_ Populatable = (*Foo)(nil)
|
|
)
|
|
|
|
func (*Foo) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Foo) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Foo) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Fooable) GetFullType() Populatable {
|
|
return &Foo{}
|
|
}
|
|
|
|
func (f *Foo) Populate() {
|
|
f.Status.Fooable = &Fooable{
|
|
// Populate ALL fields
|
|
Field1: "foo",
|
|
Field2: "bar",
|
|
}
|
|
}
|
|
|
|
// Define a "Barable" duck type.
|
|
type Barable struct {
|
|
Field1 int `json:"field1,omitempty"`
|
|
Field2 bool `json:"field2,omitempty"`
|
|
}
|
|
type Bar struct {
|
|
Status BarStatus `json:"status"`
|
|
}
|
|
type BarStatus struct {
|
|
Barable *Barable `json:"barable,omitempty"`
|
|
}
|
|
|
|
var (
|
|
_ Implementable = (*Barable)(nil)
|
|
_ Populatable = (*Bar)(nil)
|
|
)
|
|
|
|
func (*Bar) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Bar) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Bar) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Barable) GetFullType() Populatable {
|
|
return &Bar{}
|
|
}
|
|
|
|
func (f *Bar) Populate() {
|
|
f.Status.Barable = &Barable{
|
|
// Populate ALL fields
|
|
Field1: 42,
|
|
Field2: true,
|
|
}
|
|
}
|
|
|
|
// Define a "Sliceable" duck type.
|
|
type AStruct struct {
|
|
Field string `json:"field,omitempty"`
|
|
}
|
|
type (
|
|
Sliceable []AStruct
|
|
Slice struct {
|
|
Status SliceStatus `json:"status"`
|
|
}
|
|
)
|
|
|
|
type SliceStatus struct {
|
|
Sliceable *Sliceable `json:"sliceable,omitempty"`
|
|
}
|
|
|
|
var (
|
|
_ Implementable = (*Sliceable)(nil)
|
|
_ Populatable = (*Slice)(nil)
|
|
)
|
|
|
|
func (*Slice) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Slice) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Slice) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Sliceable) GetFullType() Populatable {
|
|
return &Slice{}
|
|
}
|
|
|
|
func (f *Slice) Populate() {
|
|
f.Status.Sliceable = &Sliceable{{"foo"}, {"bar"}}
|
|
}
|
|
|
|
// Define a "Stringable" duck type.
|
|
type (
|
|
Stringable string
|
|
String struct {
|
|
Status StringStatus `json:"status"`
|
|
}
|
|
)
|
|
|
|
type StringStatus struct {
|
|
Stringable Stringable `json:"stringable,omitempty"`
|
|
}
|
|
|
|
var (
|
|
_ Implementable = (*Stringable)(nil)
|
|
_ Populatable = (*String)(nil)
|
|
)
|
|
|
|
func (*String) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*String) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*String) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*Stringable) GetFullType() Populatable {
|
|
return &String{}
|
|
}
|
|
|
|
func (f *String) Populate() {
|
|
f.Status.Stringable = Stringable("hello duck")
|
|
}
|
|
|
|
// We have to do this for Stringable because we're aliasing a value type.
|
|
var emptyStringable Stringable
|
|
|
|
// For testing this doubles as the 'Implementable'
|
|
// and 'Populatable'
|
|
type UnableToMarshal struct{}
|
|
|
|
var (
|
|
_ Implementable = (*UnableToMarshal)(nil)
|
|
_ Populatable = (*UnableToMarshal)(nil)
|
|
)
|
|
|
|
func (*UnableToMarshal) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnableToMarshal) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnableToMarshal) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (u *UnableToMarshal) GetFullType() Populatable {
|
|
return u
|
|
}
|
|
|
|
func (*UnableToMarshal) Populate() {
|
|
}
|
|
|
|
func (*UnableToMarshal) MarshalJSON() ([]byte, error) {
|
|
return nil, errors.New("I will never marshal for you")
|
|
}
|
|
|
|
// For testing this doubles as the 'Implementable'
|
|
// and 'Populatable'
|
|
type UnableToUnmarshal struct{}
|
|
|
|
var (
|
|
_ Implementable = (*UnableToUnmarshal)(nil)
|
|
_ Populatable = (*UnableToUnmarshal)(nil)
|
|
)
|
|
|
|
func (*UnableToUnmarshal) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnableToUnmarshal) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnableToUnmarshal) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (u *UnableToUnmarshal) GetFullType() Populatable {
|
|
return u
|
|
}
|
|
|
|
func (*UnableToUnmarshal) Populate() {
|
|
}
|
|
|
|
func (*UnableToUnmarshal) UnmarshalJSON([]byte) error {
|
|
return errors.New("I will never unmarshal for you")
|
|
}
|
|
|
|
// For testing this doubles as the 'Implementable'
|
|
// and 'Populatable'
|
|
type UnexportedFields struct {
|
|
a string
|
|
}
|
|
|
|
var (
|
|
_ Implementable = (*UnexportedFields)(nil)
|
|
_ Populatable = (*UnexportedFields)(nil)
|
|
)
|
|
|
|
func (*UnexportedFields) GetObjectKind() schema.ObjectKind {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnexportedFields) DeepCopyObject() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnexportedFields) GetListType() runtime.Object {
|
|
return nil // not used
|
|
}
|
|
|
|
func (*UnexportedFields) GetFullType() Populatable {
|
|
return &UnexportedFields{}
|
|
}
|
|
|
|
func (u *UnexportedFields) Populate() {
|
|
u.a = "hello"
|
|
}
|