sigs.k8s.io/controller-tools@v0.15.1-0.20240515195456-85686cb69316/pkg/crd/schema_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes 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 crd
    18  
    19  import (
    20  	"go/ast"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/onsi/gomega"
    25  	"golang.org/x/tools/go/packages"
    26  	pkgstest "golang.org/x/tools/go/packages/packagestest"
    27  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    28  	crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
    29  	testloader "sigs.k8s.io/controller-tools/pkg/loader/testutils"
    30  	"sigs.k8s.io/controller-tools/pkg/markers"
    31  )
    32  
    33  func transform(t *testing.T, expr string) *apiext.JSONSchemaProps {
    34  	// this is *very* hacky but I haven’t found a simple way
    35  	// to get an ast.Expr with all the associated metadata required
    36  	// to run typeToSchema upon it:
    37  
    38  	moduleName := "sigs.k8s.io/controller-tools/pkg/crd"
    39  	modules := []pkgstest.Module{
    40  		{
    41  			Name: moduleName,
    42  			Files: map[string]interface{}{
    43  				"test.go": `
    44  				package crd 
    45  				type Test ` + expr,
    46  			},
    47  		},
    48  	}
    49  
    50  	pkgs, exported, err := testloader.LoadFakeRoots(pkgstest.Modules, modules, moduleName)
    51  	if exported != nil {
    52  		t.Cleanup(exported.Cleanup)
    53  	}
    54  
    55  	if err != nil {
    56  		t.Fatalf("unable to load fake package: %s", err)
    57  	}
    58  
    59  	if len(pkgs) != 1 {
    60  		t.Fatal("expected to parse only one package")
    61  	}
    62  
    63  	pkg := pkgs[0]
    64  	pkg.NeedTypesInfo()
    65  	failIfErrors(t, pkg.Errors)
    66  
    67  	schemaContext := newSchemaContext(pkg, nil, true, false).ForInfo(&markers.TypeInfo{})
    68  	// yick: grab the only type definition
    69  	definedType := pkg.Syntax[0].Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type
    70  	result := typeToSchema(schemaContext, definedType)
    71  	failIfErrors(t, pkg.Errors)
    72  	return result
    73  }
    74  
    75  func failIfErrors(t *testing.T, errs []packages.Error) {
    76  	if len(errs) > 0 {
    77  		var msgs []string
    78  		for _, e := range errs {
    79  			msgs = append(msgs, e.Msg)
    80  		}
    81  
    82  		t.Fatalf("error loading fake package: %s", strings.Join(msgs, "; "))
    83  	}
    84  }
    85  
    86  var arrayOfNumbersSchema *apiext.JSONSchemaProps = &apiext.JSONSchemaProps{
    87  	Type: "array",
    88  	Items: &apiext.JSONSchemaPropsOrArray{
    89  		Schema: &apiext.JSONSchemaProps{
    90  			Type: "number",
    91  		},
    92  	},
    93  }
    94  
    95  func Test_Schema_ArrayOfFloat32(t *testing.T) {
    96  	g := gomega.NewWithT(t)
    97  
    98  	output := transform(t, "[]float32")
    99  	g.Expect(output).To(gomega.Equal(arrayOfNumbersSchema))
   100  }
   101  
   102  func Test_Schema_MapOfStringToArrayOfFloat32(t *testing.T) {
   103  	g := gomega.NewWithT(t)
   104  
   105  	output := transform(t, "map[string][]float32")
   106  	g.Expect(output).To(gomega.Equal(&apiext.JSONSchemaProps{
   107  		Type: "object",
   108  		AdditionalProperties: &apiext.JSONSchemaPropsOrBool{
   109  			Allows: true,
   110  			Schema: arrayOfNumbersSchema,
   111  		},
   112  	}))
   113  }
   114  
   115  func Test_Schema_ApplyMarkers(t *testing.T) {
   116  	g := gomega.NewWithT(t)
   117  
   118  	props := &apiext.JSONSchemaProps{}
   119  	ctx := &schemaContext{}
   120  
   121  	var invocations []string
   122  
   123  	applyMarkers(ctx, markers.MarkerValues{
   124  		"blah": []interface{}{
   125  			&testPriorityMarker{
   126  				priority: 0, callback: func() {
   127  					invocations = append(invocations, "0")
   128  				},
   129  			},
   130  			&testPriorityMarker{priority: 2, callback: func() {
   131  				invocations = append(invocations, "2")
   132  			}},
   133  			&testPriorityMarker{priority: 11, callback: func() {
   134  				invocations = append(invocations, "11")
   135  			}},
   136  			&defaultPriorityMarker{callback: func() {
   137  				invocations = append(invocations, "default")
   138  			}},
   139  			&testapplyFirstMarker{callback: func() {
   140  				invocations = append(invocations, "applyFirst")
   141  			}},
   142  		}}, props, nil)
   143  
   144  	g.Expect(invocations).To(gomega.Equal([]string{"0", "applyFirst", "2", "default", "11"}))
   145  }
   146  
   147  type defaultPriorityMarker struct {
   148  	callback func()
   149  }
   150  
   151  func (m *defaultPriorityMarker) ApplyToSchema(*apiext.JSONSchemaProps) error {
   152  	m.callback()
   153  	return nil
   154  }
   155  
   156  type testPriorityMarker struct {
   157  	priority crdmarkers.ApplyPriority
   158  	callback func()
   159  }
   160  
   161  func (m *testPriorityMarker) ApplyPriority() crdmarkers.ApplyPriority {
   162  	return m.priority
   163  }
   164  
   165  func (m *testPriorityMarker) ApplyToSchema(*apiext.JSONSchemaProps) error {
   166  	m.callback()
   167  	return nil
   168  }
   169  
   170  type testapplyFirstMarker struct {
   171  	callback func()
   172  }
   173  
   174  func (m *testapplyFirstMarker) ApplyFirst() {}
   175  func (m *testapplyFirstMarker) ApplyToSchema(*apiext.JSONSchemaProps) error {
   176  	m.callback()
   177  	return nil
   178  }