...

Source file src/sigs.k8s.io/structured-merge-diff/v4/typed/validate_test.go

Documentation: sigs.k8s.io/structured-merge-diff/v4/typed

     1  /*
     2  Copyright 2018 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 typed_test
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"sigs.k8s.io/structured-merge-diff/v4/schema"
    25  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    26  )
    27  
    28  type validationTestCase struct {
    29  	name           string
    30  	rootTypeName   string
    31  	schema         typed.YAMLObject
    32  	validObjects   []typed.YAMLObject
    33  	invalidObjects []typed.YAMLObject
    34  	// duplicatesObjects are valid with AllowDuplicates validation, invalid otherwise.
    35  	duplicatesObjects []typed.YAMLObject
    36  }
    37  
    38  var validationCases = []validationTestCase{{
    39  	name:         "simple pair",
    40  	rootTypeName: "stringPair",
    41  	schema: `types:
    42  - name: stringPair
    43    map:
    44      fields:
    45      - name: key
    46        type:
    47          scalar: string
    48      - name: value
    49        type:
    50          namedType: __untyped_atomic_
    51  - name: __untyped_atomic_
    52    scalar: untyped
    53    list:
    54      elementType:
    55        namedType: __untyped_atomic_
    56      elementRelationship: atomic
    57    map:
    58      elementType:
    59        namedType: __untyped_atomic_
    60      elementRelationship: atomic
    61  `,
    62  	validObjects: []typed.YAMLObject{
    63  		`{"key":"foo","value":1}`,
    64  		`{"key":"foo","value":{}}`,
    65  		`{"key":"foo","value":null}`,
    66  		`{"key":"foo"}`,
    67  		`{"key":"foo","value":true}`,
    68  		`{"key":null}`,
    69  	},
    70  	invalidObjects: []typed.YAMLObject{
    71  		`{"key":true,"value":1}`,
    72  		`{"key":1,"value":{}}`,
    73  		`{"key":false,"value":null}`,
    74  		`{"key":[1, 2]}`,
    75  		`{"key":{"foo":true}}`,
    76  	},
    77  }, {
    78  	name:         "struct grab bag",
    79  	rootTypeName: "myStruct",
    80  	schema: `types:
    81  - name: myStruct
    82    map:
    83      fields:
    84      - name: numeric
    85        type:
    86          scalar: numeric
    87      - name: string
    88        type:
    89          scalar: string
    90      - name: bool
    91        type:
    92          scalar: boolean
    93      - name: setStr
    94        type:
    95          list:
    96            elementType:
    97              scalar: string
    98            elementRelationship: associative
    99      - name: setBool
   100        type:
   101          list:
   102            elementType:
   103              scalar: boolean
   104            elementRelationship: associative
   105      - name: setNumeric
   106        type:
   107          list:
   108            elementType:
   109              scalar: numeric
   110            elementRelationship: associative
   111  `,
   112  	validObjects: []typed.YAMLObject{
   113  		`{"numeric":null}`,
   114  		`{"numeric":1}`,
   115  		`{"numeric":3.14159}`,
   116  		`{"string":null}`,
   117  		`{"string":"aoeu"}`,
   118  		`{"bool":null}`,
   119  		`{"bool":true}`,
   120  		`{"bool":false}`,
   121  		`{"setStr":["a","b","c"]}`,
   122  		`{"setBool":[true,false]}`,
   123  		`{"setNumeric":[1,2,3,3.14159]}`,
   124  	},
   125  	invalidObjects: []typed.YAMLObject{
   126  		`{"numeric":["foo"]}`,
   127  		`{"numeric":{"a":1}}`,
   128  		`{"numeric":"foo"}`,
   129  		`{"numeric":true}`,
   130  		`{"string":1}`,
   131  		`{"string":3.5}`,
   132  		`{"string":true}`,
   133  		`{"string":{"a":1}}`,
   134  		`{"string":["foo"]}`,
   135  		`{"bool":1}`,
   136  		`{"bool":3.5}`,
   137  		`{"bool":"aoeu"}`,
   138  		`{"bool":{"a":1}}`,
   139  		`{"bool":["foo"]}`,
   140  		`{"setStr":[1]}`,
   141  		`{"setStr":[true]}`,
   142  		`{"setStr":[1.5]}`,
   143  		`{"setStr":[null]}`,
   144  		`{"setStr":[{}]}`,
   145  		`{"setStr":[[]]}`,
   146  		`{"setBool":[1]}`,
   147  		`{"setBool":[1.5]}`,
   148  		`{"setBool":[null]}`,
   149  		`{"setBool":[{}]}`,
   150  		`{"setBool":[[]]}`,
   151  		`{"setBool":["a"]}`,
   152  		`{"setNumeric":[null]}`,
   153  		`{"setNumeric":[true]}`,
   154  		`{"setNumeric":["a"]}`,
   155  		`{"setNumeric":[[]]}`,
   156  		`{"setNumeric":[{}]}`,
   157  	}, duplicatesObjects: []typed.YAMLObject{
   158  		`{"setStr":["a","a"]}`,
   159  		`{"setBool":[true,false,true]}`,
   160  		`{"setNumeric":[1,2,3,3.14159,1]}`,
   161  	},
   162  }, {
   163  	name:         "associative list",
   164  	rootTypeName: "myRoot",
   165  	schema: `types:
   166  - name: myRoot
   167    map:
   168      fields:
   169      - name: list
   170        type:
   171          namedType: myList
   172      - name: atomicList
   173        type:
   174          namedType: mySequence
   175  - name: myList
   176    list:
   177      elementType:
   178        namedType: myElement
   179      elementRelationship: associative
   180      keys:
   181      - key
   182      - id
   183  - name: mySequence
   184    list:
   185      elementType:
   186        scalar: string
   187      elementRelationship: atomic
   188  - name: myElement
   189    map:
   190      fields:
   191      - name: key
   192        type:
   193          scalar: string
   194      - name: id
   195        type:
   196          scalar: numeric
   197      - name: value
   198        type:
   199          namedType: myValue
   200      - name: bv
   201        type:
   202          scalar: boolean
   203      - name: nv
   204        type:
   205          scalar: numeric
   206  - name: myValue
   207    map:
   208      elementType:
   209        scalar: string
   210  `,
   211  	validObjects: []typed.YAMLObject{
   212  		`{"list":[]}`,
   213  		`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
   214  		`{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`,
   215  		`{"atomicList":["a","a","a"]}`,
   216  	},
   217  	invalidObjects: []typed.YAMLObject{
   218  		`{"key":true,"value":1}`,
   219  		`{"list":{"key":true,"value":1}}`,
   220  		`{"list":true}`,
   221  		`true`,
   222  		`{"list":[{"key":true,"value":1}]}`,
   223  		`{"list":[{"key":[],"value":1}]}`,
   224  		`{"list":[{"key":{},"value":1}]}`,
   225  		`{"list":[{"key":1.5,"value":1}]}`,
   226  		`{"list":[{"key":1,"value":1}]}`,
   227  		`{"list":[{"key":null,"value":1}]}`,
   228  		`{"list":[{},{}]}`,
   229  		`{"list":[{},null]}`,
   230  		`{"list":[[]]}`,
   231  		`{"list":[null]}`,
   232  		`{"list":[{}]}`,
   233  		`{"list":[{"value":{"a":"a"},"bv":true,"nv":3.14}]}`,
   234  		`{"list":[{"key":"a","id":1,"value":{"a":1}}]}`,
   235  		`{"list":[{"key":"a","id":1,"value":{"a":"a"},"bv":"true","nv":3.14}]}`,
   236  		`{"list":[{"key":"a","id":1,"value":{"a":"a"},"bv":true,"nv":false}]}`,
   237  	}, duplicatesObjects: []typed.YAMLObject{
   238  		`{"list":[{"key":"a","id":1},{"key":"a","id":1}]}`,
   239  	},
   240  }}
   241  
   242  func (tt validationTestCase) test(t *testing.T) {
   243  	parser, err := typed.NewParser(tt.schema)
   244  	if err != nil {
   245  		t.Fatalf("failed to create schema: %v", err)
   246  	}
   247  	pt := parser.Type(tt.rootTypeName)
   248  
   249  	for i, v := range tt.validObjects {
   250  		v := v
   251  		t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) {
   252  			t.Parallel()
   253  			_, err := pt.FromYAML(v)
   254  			if err != nil {
   255  				t.Errorf("failed to parse/validate yaml: %v\n%v", err, v)
   256  			}
   257  		})
   258  	}
   259  
   260  	for i, iv := range tt.invalidObjects {
   261  		iv := iv
   262  		t.Run(fmt.Sprintf("%v-invalid-%v", tt.name, i), func(t *testing.T) {
   263  			t.Parallel()
   264  			_, err := pt.FromYAML(iv)
   265  			if err == nil {
   266  				t.Fatalf("Object should fail:\n%v", iv)
   267  			}
   268  			if strings.Contains(err.Error(), "invalid atom") {
   269  				t.Errorf("Error should be useful, but got: %v\n%v", err, iv)
   270  			}
   271  		})
   272  	}
   273  	for i, iv := range tt.duplicatesObjects {
   274  		iv := iv
   275  		t.Run(fmt.Sprintf("%v-duplicates-%v", tt.name, i), func(t *testing.T) {
   276  			t.Parallel()
   277  			_, err := pt.FromYAML(iv)
   278  			if err == nil {
   279  				t.Fatalf("Object should fail:\n%v", iv)
   280  			}
   281  			if strings.Contains(err.Error(), "invalid atom") {
   282  				t.Errorf("Error should be useful, but got: %v\n%v", err, iv)
   283  			}
   284  			_, err = pt.FromYAML(iv, typed.AllowDuplicates)
   285  			if err != nil {
   286  				t.Errorf("failed to parse/validate yaml: %v\n%v", err, iv)
   287  			}
   288  		})
   289  	}
   290  }
   291  
   292  func TestSchemaValidation(t *testing.T) {
   293  	for _, tt := range validationCases {
   294  		tt := tt
   295  		t.Run(tt.name, func(t *testing.T) {
   296  			t.Parallel()
   297  			tt.test(t)
   298  		})
   299  	}
   300  }
   301  
   302  func TestSchemaSchema(t *testing.T) {
   303  	// Verify that the schema schema validates itself.
   304  	_, err := typed.NewParser(typed.YAMLObject(schema.SchemaSchemaYAML))
   305  	if err != nil {
   306  		t.Fatalf("failed to create schemaschema: %v", err)
   307  	}
   308  }
   309  
   310  func BenchmarkValidateStructured(b *testing.B) {
   311  	type Primitives struct {
   312  		s string
   313  		i int64
   314  		f float64
   315  		b bool
   316  	}
   317  
   318  	primitive1 := Primitives{s: "string1"}
   319  	primitive2 := Primitives{i: 100}
   320  	primitive3 := Primitives{f: 3.14}
   321  	primitive4 := Primitives{b: true}
   322  
   323  	type Example struct {
   324  		listOfPrimitives []Primitives
   325  		mapOfPrimitives  map[string]Primitives
   326  		mapOfLists       map[string][]Primitives
   327  	}
   328  
   329  	tests := []struct {
   330  		name         string
   331  		rootTypeName string
   332  		schema       typed.YAMLObject
   333  		object       interface{}
   334  	}{
   335  		{
   336  			name:         "struct",
   337  			rootTypeName: "example",
   338  			schema: `types:
   339  - name: example
   340    map:
   341      fields:
   342      - name: listOfPrimitives
   343        type:
   344          list:
   345            elementType:
   346              namedType: primitives
   347      - name: mapOfPrimitives
   348        type:
   349          map:
   350            elementType:
   351              namedType: primitives
   352      - name: mapOfLists
   353        type:
   354          map:
   355            elementType:
   356              list:
   357                elementType:
   358                  namedType: primitives
   359  - name: primitives
   360    map:
   361      fields:
   362      - name: s
   363        type:
   364          scalar: string
   365      - name: i
   366        type:
   367          scalar: numeric
   368      - name: f
   369        type:
   370          scalar: numeric
   371      - name: b
   372        type:
   373          scalar: boolean
   374  `,
   375  			object: &Example{
   376  				listOfPrimitives: []Primitives{primitive1, primitive2, primitive3, primitive4},
   377  				mapOfPrimitives:  map[string]Primitives{"1": primitive1, "2": primitive2, "3": primitive3, "4": primitive4},
   378  				mapOfLists: map[string][]Primitives{
   379  					"1": {primitive1, primitive2, primitive3, primitive4},
   380  					"2": {primitive1, primitive2, primitive3, primitive4},
   381  				},
   382  			},
   383  		},
   384  	}
   385  
   386  	for _, test := range tests {
   387  		b.Run(test.name, func(b *testing.B) {
   388  			parser, err := typed.NewParser(test.schema)
   389  			if err != nil {
   390  				b.Fatalf("failed to create schema: %v", err)
   391  			}
   392  			pt := parser.Type(test.rootTypeName)
   393  
   394  			b.ReportAllocs()
   395  			for n := 0; n < b.N; n++ {
   396  				tv, err := pt.FromStructured(test.object)
   397  				if err != nil {
   398  					b.Errorf("failed to parse/validate yaml: %v\n%v", err, tv)
   399  				}
   400  			}
   401  		})
   402  	}
   403  }
   404  

View as plain text