knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/verify_test.go (about)

     1  /*
     2  Copyright 2017 The Knative 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 duck
    18  
    19  import (
    20  	"errors"
    21  	"testing"
    22  
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/apimachinery/pkg/runtime/schema"
    25  )
    26  
    27  func TestMatches(t *testing.T) {
    28  	tests := []struct {
    29  		name     string
    30  		instance interface{}
    31  		iface    Implementable
    32  	}{{
    33  		name:     "foo matches fooable",
    34  		instance: &Foo{},
    35  		iface:    &Fooable{},
    36  	}, {
    37  		name:     "bar matches barable",
    38  		instance: &Bar{},
    39  		iface:    &Barable{},
    40  	}, {
    41  		name:     "slice matches sliceable",
    42  		instance: &Slice{},
    43  		iface:    &Sliceable{},
    44  	}, {
    45  		name:     "string matches stringable",
    46  		instance: &String{},
    47  		iface:    &emptyStringable,
    48  	}, {
    49  		name: "other matches foo",
    50  		instance: &struct {
    51  			Status struct {
    52  				Fooable *Fooable `json:"fooable,omitempty"`
    53  			} `json:"status,omitempty"`
    54  		}{},
    55  		iface: &Fooable{},
    56  	}, {
    57  		name: "other (all) matches fooable",
    58  		instance: &struct {
    59  			Status struct {
    60  				Fooable    *Fooable    `json:"fooable,omitempty"`
    61  				Barable    *Barable    `json:"barable,omitempty"`
    62  				Sliceable  Sliceable   `json:"sliceable,omitempty"`
    63  				Stringable *Stringable `json:"stringable,omitempty"`
    64  			} `json:"status,omitempty"`
    65  		}{},
    66  		iface: &Fooable{},
    67  	}, {
    68  		name: "other (all) matches barable",
    69  		instance: &struct {
    70  			Status struct {
    71  				Fooable    *Fooable    `json:"fooable,omitempty"`
    72  				Barable    *Barable    `json:"barable,omitempty"`
    73  				Sliceable  Sliceable   `json:"sliceable,omitempty"`
    74  				Stringable *Stringable `json:"stringable,omitempty"`
    75  			} `json:"status,omitempty"`
    76  		}{},
    77  		iface: &Barable{},
    78  	}, {
    79  		name: "other (all) matches sliceable",
    80  		instance: &struct {
    81  			Status struct {
    82  				Fooable    *Fooable    `json:"fooable,omitempty"`
    83  				Barable    *Barable    `json:"barable,omitempty"`
    84  				Sliceable  Sliceable   `json:"sliceable,omitempty"`
    85  				Stringable *Stringable `json:"stringable,omitempty"`
    86  			} `json:"status,omitempty"`
    87  		}{},
    88  		iface: &Sliceable{},
    89  	}, {
    90  		name: "other (all) matches stringable",
    91  		instance: &struct {
    92  			Status struct {
    93  				Fooable    *Fooable    `json:"fooable,omitempty"`
    94  				Barable    *Barable    `json:"barable,omitempty"`
    95  				Sliceable  Sliceable   `json:"sliceable,omitempty"`
    96  				Stringable *Stringable `json:"stringable,omitempty"`
    97  			} `json:"status,omitempty"`
    98  		}{},
    99  		iface: &emptyStringable,
   100  	}}
   101  
   102  	for _, test := range tests {
   103  		t.Run(test.name, func(t *testing.T) {
   104  			if err := VerifyType(test.instance, test.iface); err != nil {
   105  				t.Error(err)
   106  			}
   107  
   108  			ok, err := ConformsToType(test.instance, test.iface)
   109  			if err != nil {
   110  				t.Error(err)
   111  			}
   112  
   113  			if !ok {
   114  				t.Errorf("Expected %T to conform to %T", test.instance, test.iface)
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func TestMismatches(t *testing.T) {
   121  	tests := []struct {
   122  		name     string
   123  		instance interface{}
   124  		iface    Implementable
   125  	}{{
   126  		name:     "foo doesn't match barable",
   127  		instance: &Foo{},
   128  		iface:    &Barable{},
   129  	}, {
   130  		name:     "bar doesn't match fooable",
   131  		instance: &Bar{},
   132  		iface:    &Fooable{},
   133  	}, {
   134  		name:     "foo doesn't match sliceable",
   135  		instance: &Foo{},
   136  		iface:    &Sliceable{},
   137  	}, {
   138  		name: "other matches neither (foo)",
   139  		instance: &struct {
   140  			Status struct {
   141  				Done bool `json:"done,omitempty"`
   142  			} `json:"status,omitempty"`
   143  		}{},
   144  		iface: &Fooable{},
   145  	}, {
   146  		name: "other matches neither (slice)",
   147  		instance: &struct {
   148  			Status struct {
   149  				Done bool `json:"done,omitempty"`
   150  			} `json:"status,omitempty"`
   151  		}{},
   152  		iface: &Sliceable{},
   153  	}}
   154  
   155  	for _, test := range tests {
   156  		t.Run(test.name, func(t *testing.T) {
   157  			if err := VerifyType(test.instance, test.iface); err == nil {
   158  				t.Errorf("Unexpected success %T implements %T", test.instance, test.iface)
   159  			}
   160  
   161  			ok, err := ConformsToType(test.instance, test.iface)
   162  			if err != nil {
   163  				t.Error(err)
   164  			}
   165  
   166  			if ok {
   167  				t.Errorf("Expected %T to not conform to %T", test.instance, test.iface)
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func TestErrors(t *testing.T) {
   174  	tests := []struct {
   175  		name     string
   176  		instance interface{}
   177  		iface    Implementable
   178  	}{{
   179  		name:     "duck type - fails to marshal",
   180  		instance: &Foo{},
   181  		iface:    &UnableToMarshal{},
   182  	}, {
   183  		name:     "duck type - fails to unmarshal",
   184  		instance: &Foo{},
   185  		iface:    &UnableToUnmarshal{},
   186  	}, {
   187  		name:     "instance - fails to unmarshal",
   188  		instance: &UnableToUnmarshal{},
   189  		iface:    &Fooable{},
   190  	}, {
   191  		name:     "instance - fails to marshal",
   192  		instance: &UnableToMarshal{},
   193  		iface:    &Fooable{},
   194  	}, {
   195  		name:     "duck type - unexported fields",
   196  		instance: &Foo{},
   197  		iface:    &UnexportedFields{},
   198  	}}
   199  
   200  	for _, test := range tests {
   201  		t.Run(test.name, func(t *testing.T) {
   202  			if err := VerifyType(test.instance, test.iface); err == nil {
   203  				t.Error("expected VerifyType to return an error")
   204  			}
   205  
   206  			if _, err := ConformsToType(test.instance, test.iface); err == nil {
   207  				t.Error("expected ConformsToType to return an error")
   208  			}
   209  		})
   210  	}
   211  }
   212  
   213  // Define a "Fooable" duck type.
   214  type Fooable struct {
   215  	Field1 string `json:"field1,omitempty"`
   216  	Field2 string `json:"field2,omitempty"`
   217  }
   218  type Foo struct {
   219  	Status FooStatus `json:"status"`
   220  }
   221  type FooStatus struct {
   222  	Fooable *Fooable `json:"fooable,omitempty"`
   223  }
   224  
   225  var (
   226  	_ Implementable = (*Fooable)(nil)
   227  	_ Populatable   = (*Foo)(nil)
   228  )
   229  
   230  func (*Foo) GetObjectKind() schema.ObjectKind {
   231  	return nil // not used
   232  }
   233  
   234  func (*Foo) DeepCopyObject() runtime.Object {
   235  	return nil // not used
   236  }
   237  
   238  func (*Foo) GetListType() runtime.Object {
   239  	return nil // not used
   240  }
   241  
   242  func (*Fooable) GetFullType() Populatable {
   243  	return &Foo{}
   244  }
   245  
   246  func (f *Foo) Populate() {
   247  	f.Status.Fooable = &Fooable{
   248  		// Populate ALL fields
   249  		Field1: "foo",
   250  		Field2: "bar",
   251  	}
   252  }
   253  
   254  // Define a "Barable" duck type.
   255  type Barable struct {
   256  	Field1 int  `json:"field1,omitempty"`
   257  	Field2 bool `json:"field2,omitempty"`
   258  }
   259  type Bar struct {
   260  	Status BarStatus `json:"status"`
   261  }
   262  type BarStatus struct {
   263  	Barable *Barable `json:"barable,omitempty"`
   264  }
   265  
   266  var (
   267  	_ Implementable = (*Barable)(nil)
   268  	_ Populatable   = (*Bar)(nil)
   269  )
   270  
   271  func (*Bar) GetObjectKind() schema.ObjectKind {
   272  	return nil // not used
   273  }
   274  
   275  func (*Bar) DeepCopyObject() runtime.Object {
   276  	return nil // not used
   277  }
   278  
   279  func (*Bar) GetListType() runtime.Object {
   280  	return nil // not used
   281  }
   282  
   283  func (*Barable) GetFullType() Populatable {
   284  	return &Bar{}
   285  }
   286  
   287  func (f *Bar) Populate() {
   288  	f.Status.Barable = &Barable{
   289  		// Populate ALL fields
   290  		Field1: 42,
   291  		Field2: true,
   292  	}
   293  }
   294  
   295  // Define a "Sliceable" duck type.
   296  type AStruct struct {
   297  	Field string `json:"field,omitempty"`
   298  }
   299  type (
   300  	Sliceable []AStruct
   301  	Slice     struct {
   302  		Status SliceStatus `json:"status"`
   303  	}
   304  )
   305  
   306  type SliceStatus struct {
   307  	Sliceable *Sliceable `json:"sliceable,omitempty"`
   308  }
   309  
   310  var (
   311  	_ Implementable = (*Sliceable)(nil)
   312  	_ Populatable   = (*Slice)(nil)
   313  )
   314  
   315  func (*Slice) GetObjectKind() schema.ObjectKind {
   316  	return nil // not used
   317  }
   318  
   319  func (*Slice) DeepCopyObject() runtime.Object {
   320  	return nil // not used
   321  }
   322  
   323  func (*Slice) GetListType() runtime.Object {
   324  	return nil // not used
   325  }
   326  
   327  func (*Sliceable) GetFullType() Populatable {
   328  	return &Slice{}
   329  }
   330  
   331  func (f *Slice) Populate() {
   332  	f.Status.Sliceable = &Sliceable{{"foo"}, {"bar"}}
   333  }
   334  
   335  // Define a "Stringable" duck type.
   336  type (
   337  	Stringable string
   338  	String     struct {
   339  		Status StringStatus `json:"status"`
   340  	}
   341  )
   342  
   343  type StringStatus struct {
   344  	Stringable Stringable `json:"stringable,omitempty"`
   345  }
   346  
   347  var (
   348  	_ Implementable = (*Stringable)(nil)
   349  	_ Populatable   = (*String)(nil)
   350  )
   351  
   352  func (*String) GetObjectKind() schema.ObjectKind {
   353  	return nil // not used
   354  }
   355  
   356  func (*String) DeepCopyObject() runtime.Object {
   357  	return nil // not used
   358  }
   359  
   360  func (*String) GetListType() runtime.Object {
   361  	return nil // not used
   362  }
   363  
   364  func (*Stringable) GetFullType() Populatable {
   365  	return &String{}
   366  }
   367  
   368  func (f *String) Populate() {
   369  	f.Status.Stringable = Stringable("hello duck")
   370  }
   371  
   372  // We have to do this for Stringable because we're aliasing a value type.
   373  var emptyStringable Stringable
   374  
   375  // For testing this doubles as the 'Implementable'
   376  // and 'Populatable'
   377  type UnableToMarshal struct{}
   378  
   379  var (
   380  	_ Implementable = (*UnableToMarshal)(nil)
   381  	_ Populatable   = (*UnableToMarshal)(nil)
   382  )
   383  
   384  func (*UnableToMarshal) GetObjectKind() schema.ObjectKind {
   385  	return nil // not used
   386  }
   387  
   388  func (*UnableToMarshal) DeepCopyObject() runtime.Object {
   389  	return nil // not used
   390  }
   391  
   392  func (*UnableToMarshal) GetListType() runtime.Object {
   393  	return nil // not used
   394  }
   395  
   396  func (u *UnableToMarshal) GetFullType() Populatable {
   397  	return u
   398  }
   399  
   400  func (*UnableToMarshal) Populate() {
   401  }
   402  
   403  func (*UnableToMarshal) MarshalJSON() ([]byte, error) {
   404  	return nil, errors.New("I will never marshal for you")
   405  }
   406  
   407  // For testing this doubles as the 'Implementable'
   408  // and 'Populatable'
   409  type UnableToUnmarshal struct{}
   410  
   411  var (
   412  	_ Implementable = (*UnableToUnmarshal)(nil)
   413  	_ Populatable   = (*UnableToUnmarshal)(nil)
   414  )
   415  
   416  func (*UnableToUnmarshal) GetObjectKind() schema.ObjectKind {
   417  	return nil // not used
   418  }
   419  
   420  func (*UnableToUnmarshal) DeepCopyObject() runtime.Object {
   421  	return nil // not used
   422  }
   423  
   424  func (*UnableToUnmarshal) GetListType() runtime.Object {
   425  	return nil // not used
   426  }
   427  
   428  func (u *UnableToUnmarshal) GetFullType() Populatable {
   429  	return u
   430  }
   431  
   432  func (*UnableToUnmarshal) Populate() {
   433  }
   434  
   435  func (*UnableToUnmarshal) UnmarshalJSON([]byte) error {
   436  	return errors.New("I will never unmarshal for you")
   437  }
   438  
   439  // For testing this doubles as the 'Implementable'
   440  // and 'Populatable'
   441  type UnexportedFields struct {
   442  	a string
   443  }
   444  
   445  var (
   446  	_ Implementable = (*UnexportedFields)(nil)
   447  	_ Populatable   = (*UnexportedFields)(nil)
   448  )
   449  
   450  func (*UnexportedFields) GetObjectKind() schema.ObjectKind {
   451  	return nil // not used
   452  }
   453  
   454  func (*UnexportedFields) DeepCopyObject() runtime.Object {
   455  	return nil // not used
   456  }
   457  
   458  func (*UnexportedFields) GetListType() runtime.Object {
   459  	return nil // not used
   460  }
   461  
   462  func (*UnexportedFields) GetFullType() Populatable {
   463  	return &UnexportedFields{}
   464  }
   465  
   466  func (u *UnexportedFields) Populate() {
   467  	u.a = "hello"
   468  }