Skip to content

Commit

Permalink
Add check that Alpha and Beta are subsets of GA fields
Browse files Browse the repository at this point in the history
Extend CheckSchema function with Assertion that Alpha and Beta type
are subsets of GA.
  • Loading branch information
kl52752 authored and bowei committed Jan 25, 2024
1 parent 8dc5377 commit 9e221d0
Show file tree
Hide file tree
Showing 7 changed files with 568 additions and 12 deletions.
41 changes: 41 additions & 0 deletions pkg/cloud/api/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,44 @@ func checkSchema(t reflect.Type) error {

return nil
}

// CheckStructuralSubset checks if type From is the subset of To type. Type is a
// subset of another if it contains fields with the same type and name. This
// function is not symmetric. Path parameter is used for better error reporting.
func CheckStructuralSubset(p Path, from, to reflect.Type) error {
if from.Kind() != to.Kind() {
return fmt.Errorf("%s has different type: %v != %v", p, from.Kind(), to.Kind())
}
if isBasicT(from) {
return nil
}
switch from.Kind() {
case reflect.Pointer:
return CheckStructuralSubset(p.Pointer(), from.Elem(), to.Elem())

case reflect.Struct:
for i := 0; i < from.NumField(); i++ {
af := from.Field(i)
bf, exist := to.FieldByName(af.Name)
if !exist {
return fmt.Errorf("%s: type %T does not have field %v", p.String(), to, af.Name)
}
if err := CheckStructuralSubset(p.Field(af.Name), af.Type, bf.Type); err != nil {
return err
}
}
return nil

case reflect.Slice, reflect.Array:
return CheckStructuralSubset(p.AnySliceIndex(), from.Elem(), to.Elem())

case reflect.Map:
path := p.AnyMapIndex()
err := CheckStructuralSubset(path, from.Key(), to.Key())
if err != nil {
return err
}
return CheckStructuralSubset(path, from.Elem(), to.Elem())
}
return fmt.Errorf("%s Unsupported type %v", p.String(), from.Kind())
}
264 changes: 264 additions & 0 deletions pkg/cloud/api/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package api
import (
"reflect"
"testing"

teststruct "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/api/converter_test_types"
)

func TestCheckFieldsAreSet(t *testing.T) {
Expand Down Expand Up @@ -272,3 +274,265 @@ func TestCheckSchema(t *testing.T) {
})
}
}

func TestConvertToT(t *testing.T) {
t.Parallel()
intVar := int(1)
type sti struct {
I int
LS []string
}
type stiB struct {
I int
LS string
}

type St struct {
I int
St sti
PSt *sti
PS *string
LS []string
M map[string]string
}
type StA struct {
I int
RT *sti
PS *string
LS []string
M map[string]string
}
type stB struct {
I int
St sti
RT *sti
}

for _, tc := range []struct {
name string
a any
b any
wantErr bool
}{
{
name: "basic - same",
a: int(1),
b: int(1),
},
{
name: "basic - different",
a: int(1),
b: "1",
wantErr: true,
},
{
name: "basic - pointer",
a: int(1),
b: &intVar,
wantErr: true,
},
{
name: "array - same",
a: [2]int{1, 2},
b: [2]int{1, 2},
},
{
name: "array - different",
a: [2]int{1, 2},
b: [2]string{"1", "2"},
wantErr: true,
},
{
name: "array - slice",
a: [2]int{1, 2},
b: []int{},
wantErr: true,
},
{
name: "slice - same",
a: []int{},
b: []int{},
},
{
name: "slice - different",
a: []int{},
b: []string{},
wantErr: true,
},
{
name: "slice - literal",
a: []int{},
b: int(1),
wantErr: true,
},
{
name: "slice - struct",
a: []St{},
b: St{},
wantErr: true,
},
{
name: "slice with pointer - same",
a: []*St{},
b: []*St{},
},
{
name: "slice with pointer - different",
a: []*St{},
b: []*StA{},
wantErr: true,
},
{
name: "slice with pointer - slice with struct",
a: []*St{},
b: []St{},
wantErr: true,
},
{
name: "slice with pointer - pointer to struct",
a: []*St{},
b: &St{},
wantErr: true,
},
{
name: "struct - same",
a: St{},
b: St{},
},
{
name: "struct - missing field",
a: St{},
b: StA{},
wantErr: true,
},
{
name: "struct - subType",
a: teststruct.St{},
b: St{},
},
{
name: "struct - inner struct different",
a: teststruct.StA{},
b: StA{},
wantErr: true,
},
{
name: "pointer - same",
a: &St{},
b: &St{},
},
{
name: "pointer - different",
a: &St{},
b: &StA{},
wantErr: true,
},
{
name: "pointer - inner struct different",
a: &teststruct.StA{},
b: &StA{},
wantErr: true,
},
{
name: "pointer - type",
a: &St{},
b: St{},
wantErr: true,
},
{
name: "map - same",
a: map[string]St{},
b: map[string]St{},
},
{
name: "map - different key",
a: map[string]St{},
b: map[int]St{},
wantErr: true,
},
{
name: "map - different value",
a: map[string]int{},
b: map[string]St{},
wantErr: true,
},
{
name: "map - value as a pointer",
a: map[string]*St{},
b: map[string]St{},
wantErr: true,
},
{
name: "map - key as a pointer",
a: map[*St]string{},
b: map[*St]string{},
},
{
name: "map - pointer to map",
a: map[int]string{},
b: &map[int]string{},
wantErr: true,
},
{
name: "map - slice of key type",
a: map[int]string{},
b: []int{},
wantErr: true,
},
{
name: "map - slice of value type",
a: map[int]string{},
b: []string{},
wantErr: true,
},
{
name: "map - basic key type",
a: map[int]string{},
b: int(0),
wantErr: true,
},
{
name: "map - basic value type",
a: map[int]string{},
b: "string",
wantErr: true,
},
{
name: "channel",
a: make(chan int),
b: make(chan int),
wantErr: true,
},
{
name: "func",
a: func() {},
b: func() {},
wantErr: true,
},
{
name: "bool",
a: true,
b: false,
},
{
name: "bool - int",
a: true,
b: intVar,
wantErr: true,
},
{
name: "bool - pointer",
a: true,
b: &intVar,
wantErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
err := CheckStructuralSubset(Path{}, reflect.TypeOf(tc.a), reflect.TypeOf(tc.b))
gotErr := err != nil
if gotErr != tc.wantErr {
t.Fatalf("CheckStructuralSubset() = %v; gotErr = %t, want %t", err, gotErr, tc.wantErr)
}
})
}
}
52 changes: 52 additions & 0 deletions pkg/cloud/api/converter_test_types/converter-structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2024 Google LLC
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
https://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 convert_test_types

// This file contains structs for testing conversion. Type to test conversion
// need to have the same names but different schema that's new file with
// duplicated structs are needed.
type St struct {
I int
PS *string
LS []string
M map[string]string
}

type sti struct {
C int
LS []string
}

type StA struct {
I int
RT *sti
PS *string
LS []string
M map[string]string
}

type sti2 struct {
AI string
BI int
}
type StBI struct {
Name string
SelfLink string
SI *sti2
NullFields []string
ForceSendFields []string
}
Loading

0 comments on commit 9e221d0

Please sign in to comment.