kubernetes schemas_test 源码

  • 2022-09-18
  • 浏览 (199)

kubernetes schemas_test 代码

文件路径:/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go

// Copyright 2020 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 model

import (
	"reflect"
	"testing"

	"github.com/google/cel-go/common/types"

	"google.golang.org/protobuf/proto"

	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
)

func TestSchemaDeclType(t *testing.T) {
	ts := testSchema()
	cust := SchemaDeclType(ts, false)
	if cust.TypeName() != "object" {
		t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName())
	}
	if len(cust.Fields) != 4 {
		t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
	}
	for _, f := range cust.Fields {
		prop, found := ts.Properties[f.Name]
		if !found {
			t.Errorf("type field not found in schema, field: %s", f.Name)
		}
		fdv := f.DefaultValue()
		if prop.Default.Object != nil {
			pdv := types.DefaultTypeAdapter.NativeToValue(prop.Default.Object)
			if !reflect.DeepEqual(fdv, pdv) {
				t.Errorf("field and schema do not agree on default value for field: %s, field value: %v, schema default: %v", f.Name, fdv, pdv)
			}
		}
		if (prop.ValueValidation == nil || len(prop.ValueValidation.Enum) == 0) && len(f.EnumValues()) != 0 {
			t.Errorf("field had more enum values than the property. field: %s", f.Name)
		}
		if prop.ValueValidation != nil {
			fevs := f.EnumValues()
			for _, fev := range fevs {
				found := false
				for _, pev := range prop.ValueValidation.Enum {
					celpev := types.DefaultTypeAdapter.NativeToValue(pev.Object)
					if reflect.DeepEqual(fev, celpev) {
						found = true
						break
					}
				}
				if !found {
					t.Errorf(
						"could not find field enum value in property definition. field: %s, enum: %v",
						f.Name, fev)
				}
			}
		}
	}
	if ts.ValueValidation != nil {
		for _, name := range ts.ValueValidation.Required {
			df, found := cust.FindField(name)
			if !found {
				t.Errorf("custom type missing required field. field=%s", name)
			}
			if !df.Required {
				t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
			}
		}
	}
}

func TestSchemaDeclTypes(t *testing.T) {
	ts := testSchema()
	cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject")
	typeMap := FieldTypeMap("CustomObject", cust)
	nested, _ := cust.FindField("nested")
	metadata, _ := cust.FindField("metadata")
	expectedObjTypeMap := map[string]*DeclType{
		"CustomObject":          cust,
		"CustomObject.nested":   nested.Type,
		"CustomObject.metadata": metadata.Type,
	}
	objTypeMap := map[string]*DeclType{}
	for name, t := range typeMap {
		if t.IsObject() {
			objTypeMap[name] = t
		}
	}
	if len(objTypeMap) != len(expectedObjTypeMap) {
		t.Errorf("got different type set. got=%v, wanted=%v", objTypeMap, expectedObjTypeMap)
	}
	for exp, expType := range expectedObjTypeMap {
		actType, found := objTypeMap[exp]
		if !found {
			t.Errorf("missing type in rule types: %s", exp)
			continue
		}
		expT, err := expType.ExprType()
		if err != nil {
			t.Errorf("fail to get cel type: %s", err)
		}
		actT, err := actType.ExprType()
		if err != nil {
			t.Errorf("fail to get cel type: %s", err)
		}
		if !proto.Equal(expT, actT) {
			t.Errorf("incompatible CEL types. got=%v, wanted=%v", expT, actT)
		}
	}
}

func testSchema() *schema.Structural {
	// Manual construction of a schema with the following definition:
	//
	// schema:
	//   type: object
	//   metadata:
	//     custom_type: "CustomObject"
	//   required:
	//     - name
	//     - value
	//   properties:
	//     name:
	//       type: string
	//     nested:
	//       type: object
	//       properties:
	//         subname:
	//           type: string
	//         flags:
	//           type: object
	//           additionalProperties:
	//             type: boolean
	//         dates:
	//           type: array
	//           items:
	//             type: string
	//             format: date-time
	//      metadata:
	//        type: object
	//        additionalProperties:
	//          type: object
	//          properties:
	//            key:
	//              type: string
	//            values:
	//              type: array
	//              items: string
	//     value:
	//       type: integer
	//       format: int64
	//       default: 1
	//       enum: [1,2,3]
	ts := &schema.Structural{
		Generic: schema.Generic{
			Type: "object",
		},
		Properties: map[string]schema.Structural{
			"name": {
				Generic: schema.Generic{
					Type: "string",
				},
			},
			"value": {
				Generic: schema.Generic{
					Type:    "integer",
					Default: schema.JSON{Object: int64(1)},
				},
				ValueValidation: &schema.ValueValidation{
					Format: "int64",
					Enum:   []schema.JSON{{Object: int64(1)}, {Object: int64(2)}, {Object: int64(3)}},
				},
			},
			"nested": {
				Generic: schema.Generic{
					Type: "object",
				},
				Properties: map[string]schema.Structural{
					"subname": {
						Generic: schema.Generic{
							Type: "string",
						},
					},
					"flags": {
						Generic: schema.Generic{
							Type: "object",
							AdditionalProperties: &schema.StructuralOrBool{
								Structural: &schema.Structural{
									Generic: schema.Generic{
										Type: "boolean",
									},
								},
							},
						},
					},
					"dates": {
						Generic: schema.Generic{
							Type: "array",
						},
						Items: &schema.Structural{
							Generic: schema.Generic{
								Type: "string",
							},
							ValueValidation: &schema.ValueValidation{
								Format: "date-time",
							},
						},
					},
				},
			},
			"metadata": {
				Generic: schema.Generic{
					Type: "object",
				},
				Properties: map[string]schema.Structural{
					"name": {
						Generic: schema.Generic{
							Type: "string",
						},
					},
					"value": {
						Generic: schema.Generic{
							Type: "array",
						},
						Items: &schema.Structural{
							Generic: schema.Generic{
								Type: "string",
							},
						},
					},
				},
			},
		},
	}
	return ts
}

func arraySchema(arrayType, format string, maxItems *int64) *schema.Structural {
	return &schema.Structural{
		Generic: schema.Generic{
			Type: "array",
		},
		Items: &schema.Structural{
			Generic: schema.Generic{
				Type: arrayType,
			},
			ValueValidation: &schema.ValueValidation{
				Format: format,
			},
		},
		ValueValidation: &schema.ValueValidation{
			MaxItems: maxItems,
		},
	}
}

func TestEstimateMaxLengthJSON(t *testing.T) {
	type maxLengthTest struct {
		Name                string
		InputSchema         *schema.Structural
		ExpectedMaxElements int64
	}
	tests := []maxLengthTest{
		{
			Name:        "booleanArray",
			InputSchema: arraySchema("boolean", "", nil),
			// expected JSON is [true,true,...], so our length should be (maxRequestSizeBytes - 2) / 5
			ExpectedMaxElements: 629145,
		},
		{
			Name:        "durationArray",
			InputSchema: arraySchema("string", "duration", nil),
			// expected JSON is ["0","0",...] so our length should be (maxRequestSizeBytes - 2) / 4
			ExpectedMaxElements: 786431,
		},
		{
			Name:        "datetimeArray",
			InputSchema: arraySchema("string", "date-time", nil),
			// expected JSON is ["2000-01-01T01:01:01","2000-01-01T01:01:01",...] so our length should be (maxRequestSizeBytes - 2) / 22
			ExpectedMaxElements: 142987,
		},
		{
			Name:        "dateArray",
			InputSchema: arraySchema("string", "date", nil),
			// expected JSON is ["2000-01-01","2000-01-02",...] so our length should be (maxRequestSizeBytes - 2) / 13
			ExpectedMaxElements: 241978,
		},
		{
			Name:        "numberArray",
			InputSchema: arraySchema("integer", "", nil),
			// expected JSON is [0,0,...] so our length should be (maxRequestSizeBytes - 2) / 2
			ExpectedMaxElements: 1572863,
		},
		{
			Name:        "stringArray",
			InputSchema: arraySchema("string", "", nil),
			// expected JSON is ["","",...] so our length should be (maxRequestSizeBytes - 2) / 3
			ExpectedMaxElements: 1048575,
		},
		{
			Name: "stringMap",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "object",
					AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
						Generic: schema.Generic{
							Type: "string",
						},
					}},
				},
			},
			// expected JSON is {"":"","":"",...} so our length should be (3000000 - 2) / 6
			ExpectedMaxElements: 393215,
		},
		{
			Name: "objectOptionalPropertyArray",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "array",
				},
				Items: &schema.Structural{
					Generic: schema.Generic{
						Type: "object",
					},
					Properties: map[string]schema.Structural{
						"required": schema.Structural{
							Generic: schema.Generic{
								Type: "string",
							},
						},
						"optional": schema.Structural{
							Generic: schema.Generic{
								Type: "string",
							},
						},
					},
					ValueValidation: &schema.ValueValidation{
						Required: []string{"required"},
					},
				},
			},
			// expected JSON is [{"required":"",},{"required":"",},...] so our length should be (maxRequestSizeBytes - 2) / 17
			ExpectedMaxElements: 185042,
		},
		{
			Name:        "arrayWithLength",
			InputSchema: arraySchema("integer", "int64", maxPtr(10)),
			// manually set by MaxItems
			ExpectedMaxElements: 10,
		},
		{
			Name: "stringWithLength",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					MaxLength: maxPtr(20),
				},
			},
			// manually set by MaxLength, but we expect a 4x multiplier compared to the original input
			// since OpenAPIv3 maxLength uses code points, but DeclType works with bytes
			ExpectedMaxElements: 80,
		},
		{
			Name: "mapWithLength",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "object",
					AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
						Generic: schema.Generic{
							Type: "string",
						},
					}},
				},
				ValueValidation: &schema.ValueValidation{
					Format:        "string",
					MaxProperties: maxPtr(15),
				},
			},
			// manually set by MaxProperties
			ExpectedMaxElements: 15,
		},
		{
			Name: "durationMaxSize",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					Format: "duration",
				},
			},
			// should be exactly equal to maxDurationSizeJSON
			ExpectedMaxElements: maxDurationSizeJSON,
		},
		{
			Name: "dateSize",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					Format: "date",
				},
			},
			// should be exactly equal to dateSizeJSON
			ExpectedMaxElements: dateSizeJSON,
		},
		{
			Name: "maxdatetimeSize",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					Format: "date-time",
				},
			},
			// should be exactly equal to maxDatetimeSizeJSON
			ExpectedMaxElements: maxDatetimeSizeJSON,
		},
		{
			Name: "maxintOrStringSize",
			InputSchema: &schema.Structural{
				Extensions: schema.Extensions{
					XIntOrString: true,
				},
			},
			// should be exactly equal to maxRequestSizeBytes - 2 (to allow for quotes in the case of a string)
			ExpectedMaxElements: maxRequestSizeBytes - 2,
		},
		{
			Name: "objectDefaultFieldArray",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "array",
				},
				Items: &schema.Structural{
					Generic: schema.Generic{
						Type: "object",
					},
					Properties: map[string]schema.Structural{
						"field": schema.Structural{
							Generic: schema.Generic{
								Type:    "string",
								Default: schema.JSON{Object: "default"},
							},
						},
					},
					ValueValidation: &schema.ValueValidation{
						Required: []string{"field"},
					},
				},
			},
			// expected JSON is [{},{},...] so our length should be (maxRequestSizeBytes - 2) / 3
			ExpectedMaxElements: 1048575,
		},
		{
			Name: "byteStringSize",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					Format: "byte",
				},
			},
			// expected JSON is "" so our length should be (maxRequestSizeBytes - 2)
			ExpectedMaxElements: 3145726,
		},
		{
			Name: "byteStringSetMaxLength",
			InputSchema: &schema.Structural{
				Generic: schema.Generic{
					Type: "string",
				},
				ValueValidation: &schema.ValueValidation{
					Format:    "byte",
					MaxLength: maxPtr(20),
				},
			},
			// note that unlike regular strings we don't have to take unicode into account,
			// so we we expect the max length to be exactly equal to the user-supplied one
			ExpectedMaxElements: 20,
		},
	}
	for _, testCase := range tests {
		t.Run(testCase.Name, func(t *testing.T) {
			decl := SchemaDeclType(testCase.InputSchema, false)
			if decl.MaxElements != testCase.ExpectedMaxElements {
				t.Errorf("wrong maxElements (got %d, expected %d)", decl.MaxElements, testCase.ExpectedMaxElements)
			}
		})
	}
}

func maxPtr(max int64) *int64 {
	return &max
}

func genNestedSchema(depth int) *schema.Structural {
	var generator func(d int) schema.Structural
	generator = func(d int) schema.Structural {
		nodeTemplate := schema.Structural{
			Generic: schema.Generic{
				Type:                 "object",
				AdditionalProperties: &schema.StructuralOrBool{},
			},
		}
		if d == 1 {
			return nodeTemplate
		} else {
			mapType := generator(d - 1)
			nodeTemplate.Generic.AdditionalProperties.Structural = &mapType
			return nodeTemplate
		}
	}
	schema := generator(depth)
	return &schema
}

func BenchmarkDeeplyNestedSchemaDeclType(b *testing.B) {
	benchmarkSchema := genNestedSchema(10)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		SchemaDeclType(benchmarkSchema, false)
	}
}

相关信息

kubernetes 源码目录

相关文章

kubernetes escaping 源码

kubernetes escaping_test 源码

kubernetes registry 源码

kubernetes schemas 源码

kubernetes types 源码

kubernetes types_test 源码

kubernetes url 源码

kubernetes value 源码

kubernetes value_test 源码

0  赞