k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/internal/third_party/go-json-experiment/json/state_test.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package json 6 7 import ( 8 "fmt" 9 "reflect" 10 "strings" 11 "testing" 12 ) 13 14 func TestStateMachine(t *testing.T) { 15 // To test a state machine, we pass an ordered sequence of operations and 16 // check whether the current state is as expected. 17 // The operation type is a union type of various possible operations, 18 // which either call mutating methods on the state machine or 19 // call accessor methods on state machine and verify the results. 20 type operation any 21 type ( 22 // stackLengths checks the results of stateEntry.length accessors. 23 stackLengths []int 24 25 // appendTokens is sequence of token kinds to append where 26 // none of them are expected to fail. 27 // 28 // For example: `[nft]` is equivalent to the following sequence: 29 // 30 // pushArray() 31 // appendLiteral() 32 // appendString() 33 // appendNumber() 34 // popArray() 35 // 36 appendTokens string 37 38 // appendToken is a single token kind to append with the expected error. 39 appendToken struct { 40 kind Kind 41 want error 42 } 43 44 // needDelim checks the result of the needDelim accessor. 45 needDelim struct { 46 next Kind 47 want byte 48 } 49 ) 50 51 // Each entry is a sequence of tokens to pass to the state machine. 52 tests := []struct { 53 label string 54 ops []operation 55 }{{ 56 "TopLevelValues", 57 []operation{ 58 stackLengths{0}, 59 needDelim{'n', 0}, 60 appendTokens(`nft`), 61 stackLengths{3}, 62 needDelim{'"', 0}, 63 appendTokens(`"0[]{}`), 64 stackLengths{7}, 65 }, 66 }, { 67 "ArrayValues", 68 []operation{ 69 stackLengths{0}, 70 needDelim{'[', 0}, 71 appendTokens(`[`), 72 stackLengths{1, 0}, 73 needDelim{'n', 0}, 74 appendTokens(`nft`), 75 stackLengths{1, 3}, 76 needDelim{'"', ','}, 77 appendTokens(`"0[]{}`), 78 stackLengths{1, 7}, 79 needDelim{']', 0}, 80 appendTokens(`]`), 81 stackLengths{1}, 82 }, 83 }, { 84 "ObjectValues", 85 []operation{ 86 stackLengths{0}, 87 needDelim{'{', 0}, 88 appendTokens(`{`), 89 stackLengths{1, 0}, 90 needDelim{'"', 0}, 91 appendTokens(`"`), 92 stackLengths{1, 1}, 93 needDelim{'n', ':'}, 94 appendTokens(`n`), 95 stackLengths{1, 2}, 96 needDelim{'"', ','}, 97 appendTokens(`"f"t`), 98 stackLengths{1, 6}, 99 appendTokens(`"""0"[]"{}`), 100 stackLengths{1, 14}, 101 needDelim{'}', 0}, 102 appendTokens(`}`), 103 stackLengths{1}, 104 }, 105 }, { 106 "ObjectCardinality", 107 []operation{ 108 appendTokens(`{`), 109 110 // Appending any kind other than string for object name is an error. 111 appendToken{'n', errMissingName}, 112 appendToken{'f', errMissingName}, 113 appendToken{'t', errMissingName}, 114 appendToken{'0', errMissingName}, 115 appendToken{'{', errMissingName}, 116 appendToken{'[', errMissingName}, 117 appendTokens(`"`), 118 119 // Appending '}' without first appending any value is an error. 120 appendToken{'}', errMissingValue}, 121 appendTokens(`"`), 122 123 appendTokens(`}`), 124 }, 125 }, { 126 "MismatchingDelims", 127 []operation{ 128 appendToken{'}', errMismatchDelim}, // appending '}' without preceding '{' 129 appendTokens(`[[{`), 130 appendToken{']', errMismatchDelim}, // appending ']' that mismatches preceding '{' 131 appendTokens(`}]`), 132 appendToken{'}', errMismatchDelim}, // appending '}' that mismatches preceding '[' 133 appendTokens(`]`), 134 appendToken{']', errMismatchDelim}, // appending ']' without preceding '[' 135 }, 136 }} 137 138 for _, tt := range tests { 139 t.Run(tt.label, func(t *testing.T) { 140 // Flatten appendTokens to sequence of appendToken entries. 141 var ops []operation 142 for _, op := range tt.ops { 143 if toks, ok := op.(appendTokens); ok { 144 for _, k := range []byte(toks) { 145 ops = append(ops, appendToken{Kind(k), nil}) 146 } 147 continue 148 } 149 ops = append(ops, op) 150 } 151 152 // Append each token to the state machine and check the output. 153 var state stateMachine 154 state.reset() 155 var sequence []Kind 156 for _, op := range ops { 157 switch op := op.(type) { 158 case stackLengths: 159 var got []int 160 for i := 0; i < state.depth(); i++ { 161 e := state.index(i) 162 got = append(got, e.length()) 163 } 164 want := []int(op) 165 if !reflect.DeepEqual(got, want) { 166 t.Fatalf("%s: stack lengths mismatch:\ngot %v\nwant %v", sequence, got, want) 167 } 168 case appendToken: 169 got := state.append(op.kind) 170 if !reflect.DeepEqual(got, op.want) { 171 t.Fatalf("%s: append('%c') = %v, want %v", sequence, op.kind, got, op.want) 172 } 173 if got == nil { 174 sequence = append(sequence, op.kind) 175 } 176 case needDelim: 177 if got := state.needDelim(op.next); got != op.want { 178 t.Fatalf("%s: needDelim('%c') = '%c', want '%c'", sequence, op.next, got, op.want) 179 } 180 default: 181 panic(fmt.Sprintf("unknown operation: %T", op)) 182 } 183 } 184 }) 185 } 186 } 187 188 // append is a thin wrapper over the other append, pop, or push methods 189 // based on the token kind. 190 func (s *stateMachine) append(k Kind) error { 191 switch k { 192 case 'n', 'f', 't': 193 return s.appendLiteral() 194 case '"': 195 return s.appendString() 196 case '0': 197 return s.appendNumber() 198 case '{': 199 return s.pushObject() 200 case '}': 201 return s.popObject() 202 case '[': 203 return s.pushArray() 204 case ']': 205 return s.popArray() 206 default: 207 panic(fmt.Sprintf("invalid token kind: '%c'", k)) 208 } 209 } 210 211 func TestObjectNamespace(t *testing.T) { 212 type operation any 213 type ( 214 insert struct { 215 name string 216 wantInserted bool 217 } 218 removeLast struct{} 219 ) 220 221 // Sequence of insert operations to perform (order matters). 222 ops := []operation{ 223 insert{`""`, true}, 224 removeLast{}, 225 insert{`""`, true}, 226 insert{`""`, false}, 227 228 // Test insertion of the same name with different formatting. 229 insert{`"alpha"`, true}, 230 insert{`"ALPHA"`, true}, // case-sensitive matching 231 insert{`"alpha"`, false}, 232 insert{`"\u0061\u006c\u0070\u0068\u0061"`, false}, // unescapes to "alpha" 233 removeLast{}, // removes "ALPHA" 234 insert{`"alpha"`, false}, 235 removeLast{}, // removes "alpha" 236 insert{`"alpha"`, true}, 237 removeLast{}, 238 239 // Bulk insert simple names. 240 insert{`"alpha"`, true}, 241 insert{`"bravo"`, true}, 242 insert{`"charlie"`, true}, 243 insert{`"delta"`, true}, 244 insert{`"echo"`, true}, 245 insert{`"foxtrot"`, true}, 246 insert{`"golf"`, true}, 247 insert{`"hotel"`, true}, 248 insert{`"india"`, true}, 249 insert{`"juliet"`, true}, 250 insert{`"kilo"`, true}, 251 insert{`"lima"`, true}, 252 insert{`"mike"`, true}, 253 insert{`"november"`, true}, 254 insert{`"oscar"`, true}, 255 insert{`"papa"`, true}, 256 insert{`"quebec"`, true}, 257 insert{`"romeo"`, true}, 258 insert{`"sierra"`, true}, 259 insert{`"tango"`, true}, 260 insert{`"uniform"`, true}, 261 insert{`"victor"`, true}, 262 insert{`"whiskey"`, true}, 263 insert{`"xray"`, true}, 264 insert{`"yankee"`, true}, 265 insert{`"zulu"`, true}, 266 267 // Test insertion of invalid UTF-8. 268 insert{`"` + "\ufffd" + `"`, true}, 269 insert{`"` + "\ufffd" + `"`, false}, 270 insert{`"\ufffd"`, false}, // unescapes to Unicode replacement character 271 insert{`"\uFFFD"`, false}, // unescapes to Unicode replacement character 272 insert{`"` + "\xff" + `"`, false}, // mangles as Unicode replacement character 273 removeLast{}, 274 insert{`"` + "\ufffd" + `"`, true}, 275 276 // Test insertion of unicode characters. 277 insert{`"☺☻☹"`, true}, 278 insert{`"☺☻☹"`, false}, 279 removeLast{}, 280 insert{`"☺☻☹"`, true}, 281 } 282 283 // Execute the sequence of operations twice: 284 // 1) on a fresh namespace and 2) on a namespace that has been reset. 285 var ns objectNamespace 286 wantNames := []string{} 287 for _, reset := range []bool{false, true} { 288 if reset { 289 ns.reset() 290 wantNames = nil 291 } 292 293 // Execute the operations and ensure the state is consistent. 294 for i, op := range ops { 295 switch op := op.(type) { 296 case insert: 297 gotInserted := ns.insertQuoted([]byte(op.name), false) 298 if gotInserted != op.wantInserted { 299 t.Fatalf("%d: objectNamespace{%v}.insert(%v) = %v, want %v", i, strings.Join(wantNames, " "), op.name, gotInserted, op.wantInserted) 300 } 301 if gotInserted { 302 b, _ := unescapeString(nil, []byte(op.name)) 303 wantNames = append(wantNames, string(b)) 304 } 305 case removeLast: 306 ns.removeLast() 307 wantNames = wantNames[:len(wantNames)-1] 308 default: 309 panic(fmt.Sprintf("unknown operation: %T", op)) 310 } 311 312 // Check that the namespace is consistent. 313 gotNames := []string{} 314 for i := 0; i < ns.length(); i++ { 315 gotNames = append(gotNames, string(ns.getUnquoted(i))) 316 } 317 if !reflect.DeepEqual(gotNames, wantNames) { 318 t.Fatalf("%d: objectNamespace = {%v}, want {%v}", i, strings.Join(gotNames, " "), strings.Join(wantNames, " ")) 319 } 320 } 321 322 // Verify that we have not switched to using a Go map. 323 if ns.mapNames != nil { 324 t.Errorf("objectNamespace.mapNames = non-nil, want nil") 325 } 326 327 // Insert a large number of names. 328 for i := 0; i < 64; i++ { 329 ns.insertUnquoted([]byte(fmt.Sprintf(`name%d`, i))) 330 } 331 332 // Verify that we did switch to using a Go map. 333 if ns.mapNames == nil { 334 t.Errorf("objectNamespace.mapNames = nil, want non-nil") 335 } 336 } 337 } 338 339 func TestUintSet(t *testing.T) { 340 type operation any // has | insert 341 type has struct { 342 in uint 343 want bool 344 } 345 type insert struct { 346 in uint 347 want bool 348 } 349 350 // Sequence of operations to perform (order matters). 351 ops := []operation{ 352 has{0, false}, 353 has{63, false}, 354 has{64, false}, 355 has{1234, false}, 356 insert{3, true}, 357 has{2, false}, 358 has{3, true}, 359 has{4, false}, 360 has{63, false}, 361 insert{3, false}, 362 insert{63, true}, 363 has{63, true}, 364 insert{64, true}, 365 insert{64, false}, 366 has{64, true}, 367 insert{3264, true}, 368 has{3264, true}, 369 insert{3, false}, 370 has{3, true}, 371 } 372 373 var us uintSet 374 for i, op := range ops { 375 switch op := op.(type) { 376 case has: 377 if got := us.has(op.in); got != op.want { 378 t.Fatalf("%d: uintSet.has(%v) = %v, want %v", i, op.in, got, op.want) 379 } 380 case insert: 381 if got := us.insert(op.in); got != op.want { 382 t.Fatalf("%d: uintSet.insert(%v) = %v, want %v", i, op.in, got, op.want) 383 } 384 default: 385 panic(fmt.Sprintf("unknown operation: %T", op)) 386 } 387 } 388 }