k8s.io/client-go@v0.22.2/util/jsonpath/jsonpath_test.go (about) 1 /* 2 Copyright 2015 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 jsonpath 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "sort" 25 "strings" 26 "testing" 27 ) 28 29 type jsonpathTest struct { 30 name string 31 template string 32 input interface{} 33 expect string 34 expectError bool 35 } 36 37 func testJSONPath(tests []jsonpathTest, allowMissingKeys bool, t *testing.T) { 38 for _, test := range tests { 39 j := New(test.name) 40 j.AllowMissingKeys(allowMissingKeys) 41 err := j.Parse(test.template) 42 if err != nil { 43 if !test.expectError { 44 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 45 } 46 continue 47 } 48 buf := new(bytes.Buffer) 49 err = j.Execute(buf, test.input) 50 if test.expectError { 51 if err == nil { 52 t.Errorf(`in %s, expected execute error, got %q`, test.name, buf) 53 } 54 continue 55 } else if err != nil { 56 t.Errorf("in %s, execute error %v", test.name, err) 57 } 58 out := buf.String() 59 if out != test.expect { 60 t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out) 61 } 62 } 63 } 64 65 // testJSONPathSortOutput test cases related to map, the results may print in random order 66 func testJSONPathSortOutput(tests []jsonpathTest, t *testing.T) { 67 for _, test := range tests { 68 j := New(test.name) 69 err := j.Parse(test.template) 70 if err != nil { 71 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 72 } 73 buf := new(bytes.Buffer) 74 err = j.Execute(buf, test.input) 75 if err != nil { 76 t.Errorf("in %s, execute error %v", test.name, err) 77 } 78 out := buf.String() 79 //since map is visited in random order, we need to sort the results. 80 sortedOut := strings.Fields(out) 81 sort.Strings(sortedOut) 82 sortedExpect := strings.Fields(test.expect) 83 sort.Strings(sortedExpect) 84 if !reflect.DeepEqual(sortedOut, sortedExpect) { 85 t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out) 86 } 87 } 88 } 89 90 func testFailJSONPath(tests []jsonpathTest, t *testing.T) { 91 for _, test := range tests { 92 j := New(test.name) 93 err := j.Parse(test.template) 94 if err != nil { 95 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 96 } 97 buf := new(bytes.Buffer) 98 err = j.Execute(buf, test.input) 99 var out string 100 if err == nil { 101 out = "nil" 102 } else { 103 out = err.Error() 104 } 105 if out != test.expect { 106 t.Errorf("in %s, expect to get error %q, got %q", test.name, test.expect, out) 107 } 108 } 109 } 110 111 func TestTypesInput(t *testing.T) { 112 types := map[string]interface{}{ 113 "bools": []bool{true, false, true, false}, 114 "integers": []int{1, 2, 3, 4}, 115 "floats": []float64{1.0, 2.2, 3.3, 4.0}, 116 "strings": []string{"one", "two", "three", "four"}, 117 "interfaces": []interface{}{true, "one", 1, 1.1}, 118 "maps": []map[string]interface{}{ 119 {"name": "one", "value": 1}, 120 {"name": "two", "value": 2.02}, 121 {"name": "three", "value": 3.03}, 122 {"name": "four", "value": 4.04}, 123 }, 124 "structs": []struct { 125 Name string `json:"name"` 126 Value interface{} `json:"value"` 127 Type string `json:"type"` 128 }{ 129 {Name: "one", Value: 1, Type: "integer"}, 130 {Name: "two", Value: 2.002, Type: "float"}, 131 {Name: "three", Value: 3, Type: "integer"}, 132 {Name: "four", Value: 4.004, Type: "float"}, 133 }, 134 } 135 136 sliceTests := []jsonpathTest{ 137 // boolean slice tests 138 {"boolSlice", `{ .bools }`, types, `[true,false,true,false]`, false}, 139 {"boolSliceIndex", `{ .bools[0] }`, types, `true`, false}, 140 {"boolSliceIndex", `{ .bools[-1] }`, types, `false`, false}, 141 {"boolSubSlice", `{ .bools[0:2] }`, types, `true false`, false}, 142 {"boolSubSliceFirst2", `{ .bools[:2] }`, types, `true false`, false}, 143 {"boolSubSliceStep2", `{ .bools[:4:2] }`, types, `true true`, false}, 144 // integer slice tests 145 {"integerSlice", `{ .integers }`, types, `[1,2,3,4]`, false}, 146 {"integerSliceIndex", `{ .integers[0] }`, types, `1`, false}, 147 {"integerSliceIndexReverse", `{ .integers[-2] }`, types, `3`, false}, 148 {"integerSubSliceFirst2", `{ .integers[0:2] }`, types, `1 2`, false}, 149 {"integerSubSliceFirst2Alt", `{ .integers[:2] }`, types, `1 2`, false}, 150 {"integerSubSliceStep2", `{ .integers[:4:2] }`, types, `1 3`, false}, 151 // float slice tests 152 {"floatSlice", `{ .floats }`, types, `[1,2.2,3.3,4]`, false}, 153 {"floatSliceIndex", `{ .floats[0] }`, types, `1`, false}, 154 {"floatSliceIndexReverse", `{ .floats[-2] }`, types, `3.3`, false}, 155 {"floatSubSliceFirst2", `{ .floats[0:2] }`, types, `1 2.2`, false}, 156 {"floatSubSliceFirst2Alt", `{ .floats[:2] }`, types, `1 2.2`, false}, 157 {"floatSubSliceStep2", `{ .floats[:4:2] }`, types, `1 3.3`, false}, 158 // strings slice tests 159 {"stringSlice", `{ .strings }`, types, `["one","two","three","four"]`, false}, 160 {"stringSliceIndex", `{ .strings[0] }`, types, `one`, false}, 161 {"stringSliceIndexReverse", `{ .strings[-2] }`, types, `three`, false}, 162 {"stringSubSliceFirst2", `{ .strings[0:2] }`, types, `one two`, false}, 163 {"stringSubSliceFirst2Alt", `{ .strings[:2] }`, types, `one two`, false}, 164 {"stringSubSliceStep2", `{ .strings[:4:2] }`, types, `one three`, false}, 165 // interfaces slice tests 166 {"interfaceSlice", `{ .interfaces }`, types, `[true,"one",1,1.1]`, false}, 167 {"interfaceSliceIndex", `{ .interfaces[0] }`, types, `true`, false}, 168 {"interfaceSliceIndexReverse", `{ .interfaces[-2] }`, types, `1`, false}, 169 {"interfaceSubSliceFirst2", `{ .interfaces[0:2] }`, types, `true one`, false}, 170 {"interfaceSubSliceFirst2Alt", `{ .interfaces[:2] }`, types, `true one`, false}, 171 {"interfaceSubSliceStep2", `{ .interfaces[:4:2] }`, types, `true 1`, false}, 172 // maps slice tests 173 {"mapSlice", `{ .maps }`, types, 174 `[{"name":"one","value":1},{"name":"two","value":2.02},{"name":"three","value":3.03},{"name":"four","value":4.04}]`, false}, 175 {"mapSliceIndex", `{ .maps[0] }`, types, `{"name":"one","value":1}`, false}, 176 {"mapSliceIndexReverse", `{ .maps[-2] }`, types, `{"name":"three","value":3.03}`, false}, 177 {"mapSubSliceFirst2", `{ .maps[0:2] }`, types, `{"name":"one","value":1} {"name":"two","value":2.02}`, false}, 178 {"mapSubSliceFirst2Alt", `{ .maps[:2] }`, types, `{"name":"one","value":1} {"name":"two","value":2.02}`, false}, 179 {"mapSubSliceStepOdd", `{ .maps[::2] }`, types, `{"name":"one","value":1} {"name":"three","value":3.03}`, false}, 180 {"mapSubSliceStepEven", `{ .maps[1::2] }`, types, `{"name":"two","value":2.02} {"name":"four","value":4.04}`, false}, 181 // structs slice tests 182 {"structSlice", `{ .structs }`, types, 183 `[{"name":"one","value":1,"type":"integer"},{"name":"two","value":2.002,"type":"float"},{"name":"three","value":3,"type":"integer"},{"name":"four","value":4.004,"type":"float"}]`, false}, 184 {"structSliceIndex", `{ .structs[0] }`, types, `{"name":"one","value":1,"type":"integer"}`, false}, 185 {"structSliceIndexReverse", `{ .structs[-2] }`, types, `{"name":"three","value":3,"type":"integer"}`, false}, 186 {"structSubSliceFirst2", `{ .structs[0:2] }`, types, 187 `{"name":"one","value":1,"type":"integer"} {"name":"two","value":2.002,"type":"float"}`, false}, 188 {"structSubSliceFirst2Alt", `{ .structs[:2] }`, types, 189 `{"name":"one","value":1,"type":"integer"} {"name":"two","value":2.002,"type":"float"}`, false}, 190 {"structSubSliceStepOdd", `{ .structs[::2] }`, types, 191 `{"name":"one","value":1,"type":"integer"} {"name":"three","value":3,"type":"integer"}`, false}, 192 {"structSubSliceStepEven", `{ .structs[1::2] }`, types, 193 `{"name":"two","value":2.002,"type":"float"} {"name":"four","value":4.004,"type":"float"}`, false}, 194 } 195 196 testJSONPath(sliceTests, false, t) 197 } 198 199 type book struct { 200 Category string 201 Author string 202 Title string 203 Price float32 204 } 205 206 func (b book) String() string { 207 return fmt.Sprintf("{Category: %s, Author: %s, Title: %s, Price: %v}", b.Category, b.Author, b.Title, b.Price) 208 } 209 210 type bicycle struct { 211 Color string 212 Price float32 213 IsNew bool 214 } 215 216 type empName string 217 type job string 218 type store struct { 219 Book []book 220 Bicycle []bicycle 221 Name string 222 Labels map[string]int 223 Employees map[empName]job 224 } 225 226 func TestStructInput(t *testing.T) { 227 228 storeData := store{ 229 Name: "jsonpath", 230 Book: []book{ 231 {"reference", "Nigel Rees", "Sayings of the Centurey", 8.95}, 232 {"fiction", "Evelyn Waugh", "Sword of Honour", 12.99}, 233 {"fiction", "Herman Melville", "Moby Dick", 8.99}, 234 }, 235 Bicycle: []bicycle{ 236 {"red", 19.95, true}, 237 {"green", 20.01, false}, 238 }, 239 Labels: map[string]int{ 240 "engieer": 10, 241 "web/html": 15, 242 "k8s-app": 20, 243 }, 244 Employees: map[empName]job{ 245 "jason": "manager", 246 "dan": "clerk", 247 }, 248 } 249 250 storeTests := []jsonpathTest{ 251 {"plain", "hello jsonpath", nil, "hello jsonpath", false}, 252 {"recursive", "{..}", []int{1, 2, 3}, "[1,2,3]", false}, 253 {"filter", "{[?(@<5)]}", []int{2, 6, 3, 7}, "2 3", false}, 254 {"quote", `{"{"}`, nil, "{", false}, 255 {"union", "{[1,3,4]}", []int{0, 1, 2, 3, 4}, "1 3 4", false}, 256 {"array", "{[0:2]}", []string{"Monday", "Tudesday"}, "Monday Tudesday", false}, 257 {"variable", "hello {.Name}", storeData, "hello jsonpath", false}, 258 {"dict/", "{$.Labels.web/html}", storeData, "15", false}, 259 {"dict/", "{$.Employees.jason}", storeData, "manager", false}, 260 {"dict/", "{$.Employees.dan}", storeData, "clerk", false}, 261 {"dict-", "{.Labels.k8s-app}", storeData, "20", false}, 262 {"nest", "{.Bicycle[*].Color}", storeData, "red green", false}, 263 {"allarray", "{.Book[*].Author}", storeData, "Nigel Rees Evelyn Waugh Herman Melville", false}, 264 {"allfields", `{range .Bicycle[*]}{ "{" }{ @.* }{ "} " }{end}`, storeData, "{red 19.95 true} {green 20.01 false} ", false}, 265 {"recurfields", "{..Price}", storeData, "8.95 12.99 8.99 19.95 20.01", false}, 266 {"recurdotfields", "{...Price}", storeData, "8.95 12.99 8.99 19.95 20.01", false}, 267 {"superrecurfields", "{............................................................Price}", storeData, "", true}, 268 {"allstructsSlice", "{.Bicycle}", storeData, 269 `[{"Color":"red","Price":19.95,"IsNew":true},{"Color":"green","Price":20.01,"IsNew":false}]`, false}, 270 {"allstructs", `{range .Bicycle[*]}{ @ }{ " " }{end}`, storeData, 271 `{"Color":"red","Price":19.95,"IsNew":true} {"Color":"green","Price":20.01,"IsNew":false} `, false}, 272 {"lastarray", "{.Book[-1:]}", storeData, 273 `{"Category":"fiction","Author":"Herman Melville","Title":"Moby Dick","Price":8.99}`, false}, 274 {"recurarray", "{..Book[2]}", storeData, 275 `{"Category":"fiction","Author":"Herman Melville","Title":"Moby Dick","Price":8.99}`, false}, 276 {"bool", "{.Bicycle[?(@.IsNew==true)]}", storeData, `{"Color":"red","Price":19.95,"IsNew":true}`, false}, 277 } 278 279 testJSONPath(storeTests, false, t) 280 281 missingKeyTests := []jsonpathTest{ 282 {"nonexistent field", "{.hello}", storeData, "", false}, 283 {"nonexistent field 2", "before-{.hello}after", storeData, "before-after", false}, 284 } 285 testJSONPath(missingKeyTests, true, t) 286 287 failStoreTests := []jsonpathTest{ 288 {"invalid identifier", "{hello}", storeData, "unrecognized identifier hello", false}, 289 {"nonexistent field", "{.hello}", storeData, "hello is not found", false}, 290 {"invalid array", "{.Labels[0]}", storeData, "map[string]int is not array or slice", false}, 291 {"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>", false}, 292 {"redundant end", "{range .Labels.*}{@}{end}{end}", storeData, "not in range, nothing to end", false}, 293 } 294 testFailJSONPath(failStoreTests, t) 295 } 296 297 func TestJSONInput(t *testing.T) { 298 var pointsJSON = []byte(`[ 299 {"id": "i1", "x":4, "y":-5}, 300 {"id": "i2", "x":-2, "y":-5, "z":1}, 301 {"id": "i3", "x": 8, "y": 3 }, 302 {"id": "i4", "x": -6, "y": -1 }, 303 {"id": "i5", "x": 0, "y": 2, "z": 1 }, 304 {"id": "i6", "x": 1, "y": 4 } 305 ]`) 306 var pointsData interface{} 307 err := json.Unmarshal(pointsJSON, &pointsData) 308 if err != nil { 309 t.Error(err) 310 } 311 pointsTests := []jsonpathTest{ 312 {"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5", false}, 313 {"bracket key", "{[0]['id']}", pointsData, "i1", false}, 314 } 315 testJSONPath(pointsTests, false, t) 316 } 317 318 // TestKubernetes tests some use cases from kubernetes 319 func TestKubernetes(t *testing.T) { 320 var input = []byte(`{ 321 "kind": "List", 322 "items":[ 323 { 324 "kind":"None", 325 "metadata":{ 326 "name":"127.0.0.1", 327 "labels":{ 328 "kubernetes.io/hostname":"127.0.0.1" 329 } 330 }, 331 "status":{ 332 "capacity":{"cpu":"4"}, 333 "ready": true, 334 "addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}] 335 } 336 }, 337 { 338 "kind":"None", 339 "metadata":{ 340 "name":"127.0.0.2", 341 "labels":{ 342 "kubernetes.io/hostname":"127.0.0.2" 343 } 344 }, 345 "status":{ 346 "capacity":{"cpu":"8"}, 347 "ready": false, 348 "addresses":[ 349 {"type": "LegacyHostIP", "address":"127.0.0.2"}, 350 {"type": "another", "address":"127.0.0.3"} 351 ] 352 } 353 } 354 ], 355 "users":[ 356 { 357 "name": "myself", 358 "user": {} 359 }, 360 { 361 "name": "e2e", 362 "user": {"username": "admin", "password": "secret"} 363 } 364 ] 365 }`) 366 var nodesData interface{} 367 err := json.Unmarshal(input, &nodesData) 368 if err != nil { 369 t.Error(err) 370 } 371 372 nodesTests := []jsonpathTest{ 373 {"range item", `{range .items[*]}{.metadata.name}, {end}{.kind}`, nodesData, "127.0.0.1, 127.0.0.2, List", false}, 374 {"range item with quote", `{range .items[*]}{.metadata.name}{"\t"}{end}`, nodesData, "127.0.0.1\t127.0.0.2\t", false}, 375 {"range addresss", `{.items[*].status.addresses[*].address}`, nodesData, 376 "127.0.0.1 127.0.0.2 127.0.0.3", false}, 377 {"double range", `{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}`, nodesData, 378 "127.0.0.1, 127.0.0.2, 127.0.0.3, ", false}, 379 {"item name", `{.items[*].metadata.name}`, nodesData, "127.0.0.1 127.0.0.2", false}, 380 {"union nodes capacity", `{.items[*]['metadata.name', 'status.capacity']}`, nodesData, 381 `127.0.0.1 127.0.0.2 {"cpu":"4"} {"cpu":"8"}`, false}, 382 {"range nodes capacity", `{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}`, nodesData, 383 `[127.0.0.1, {"cpu":"4"}] [127.0.0.2, {"cpu":"8"}] `, false}, 384 {"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret", false}, 385 {"hostname", `{.items[0].metadata.labels.kubernetes\.io/hostname}`, &nodesData, "127.0.0.1", false}, 386 {"hostname filter", `{.items[?(@.metadata.labels.kubernetes\.io/hostname=="127.0.0.1")].kind}`, &nodesData, "None", false}, 387 {"bool item", `{.items[?(@..ready==true)].metadata.name}`, &nodesData, "127.0.0.1", false}, 388 } 389 testJSONPath(nodesTests, false, t) 390 391 randomPrintOrderTests := []jsonpathTest{ 392 {"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`, false}, 393 } 394 testJSONPathSortOutput(randomPrintOrderTests, t) 395 } 396 397 func TestEmptyRange(t *testing.T) { 398 var input = []byte(`{"items":[]}`) 399 var emptyList interface{} 400 err := json.Unmarshal(input, &emptyList) 401 if err != nil { 402 t.Error(err) 403 } 404 405 tests := []jsonpathTest{ 406 {"empty range", `{range .items[*]}{.metadata.name}{end}`, &emptyList, "", false}, 407 {"empty nested range", `{range .items[*]}{.metadata.name}{":"}{range @.spec.containers[*]}{.name}{","}{end}{"+"}{end}`, &emptyList, "", false}, 408 } 409 testJSONPath(tests, true, t) 410 } 411 412 func TestNestedRanges(t *testing.T) { 413 var input = []byte(`{ 414 "items": [ 415 { 416 "metadata": { 417 "name": "pod1" 418 }, 419 "spec": { 420 "containers": [ 421 { 422 "name": "foo", 423 "another": [ 424 { "name": "value1" }, 425 { "name": "value2" } 426 ] 427 }, 428 { 429 "name": "bar", 430 "another": [ 431 { "name": "value1" }, 432 { "name": "value2" } 433 ] 434 } 435 ] 436 } 437 }, 438 { 439 "metadata": { 440 "name": "pod2" 441 }, 442 "spec": { 443 "containers": [ 444 { 445 "name": "baz", 446 "another": [ 447 { "name": "value1" }, 448 { "name": "value2" } 449 ] 450 } 451 ] 452 } 453 } 454 ] 455 }`) 456 var data interface{} 457 err := json.Unmarshal(input, &data) 458 if err != nil { 459 t.Error(err) 460 } 461 462 testJSONPath( 463 []jsonpathTest{ 464 { 465 "nested range with a trailing newline", 466 `{range .items[*]}` + 467 `{.metadata.name}` + 468 `{":"}` + 469 `{range @.spec.containers[*]}` + 470 `{.name}` + 471 `{","}` + 472 `{end}` + 473 `{"+"}` + 474 `{end}`, 475 data, 476 "pod1:foo,bar,+pod2:baz,+", 477 false, 478 }, 479 }, 480 false, 481 t, 482 ) 483 484 testJSONPath( 485 []jsonpathTest{ 486 { 487 "nested range with a trailing character within another nested range with a trailing newline", 488 `{range .items[*]}` + 489 `{.metadata.name}` + 490 `{"~"}` + 491 `{range @.spec.containers[*]}` + 492 `{.name}` + 493 `{":"}` + 494 `{range @.another[*]}` + 495 `{.name}` + 496 `{","}` + 497 `{end}` + 498 `{"+"}` + 499 `{end}` + 500 `{"#"}` + 501 `{end}`, 502 data, 503 "pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#", 504 false, 505 }, 506 }, 507 false, 508 t, 509 ) 510 511 testJSONPath( 512 []jsonpathTest{ 513 { 514 "two nested ranges at the same level with a trailing newline", 515 `{range .items[*]}` + 516 `{.metadata.name}` + 517 `{"\t"}` + 518 `{range @.spec.containers[*]}` + 519 `{.name}` + 520 `{" "}` + 521 `{end}` + 522 `{"\t"}` + 523 `{range @.spec.containers[*]}` + 524 `{.name}` + 525 `{" "}` + 526 `{end}` + 527 `{"\n"}` + 528 `{end}`, 529 data, 530 "pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n", 531 false, 532 }, 533 }, 534 false, 535 t, 536 ) 537 } 538 539 func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) { 540 // for https://issues.k8s.io/45546 541 var input = []byte(`{ 542 "kind": "List", 543 "items": [ 544 { 545 "kind": "Pod", 546 "metadata": { 547 "name": "pod1", 548 "annotations": { 549 "color": "blue" 550 } 551 } 552 }, 553 { 554 "kind": "Pod", 555 "metadata": { 556 "name": "pod2" 557 } 558 }, 559 { 560 "kind": "Pod", 561 "metadata": { 562 "name": "pod3", 563 "annotations": { 564 "color": "green" 565 } 566 } 567 }, 568 { 569 "kind": "Pod", 570 "metadata": { 571 "name": "pod4", 572 "annotations": { 573 "color": "blue" 574 } 575 } 576 } 577 ] 578 }`) 579 var data interface{} 580 err := json.Unmarshal(input, &data) 581 if err != nil { 582 t.Fatal(err) 583 } 584 585 testJSONPath( 586 []jsonpathTest{ 587 { 588 "filter, should only match a subset, some items don't have annotations, tolerate missing items", 589 `{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`, 590 data, 591 "pod1 pod4", 592 false, // expect no error 593 }, 594 }, 595 true, // allow missing keys 596 t, 597 ) 598 599 testJSONPath( 600 []jsonpathTest{ 601 { 602 "filter, should only match a subset, some items don't have annotations, error on missing items", 603 `{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`, 604 data, 605 "", 606 true, // expect an error 607 }, 608 }, 609 false, // don't allow missing keys 610 t, 611 ) 612 } 613 614 func TestNegativeIndex(t *testing.T) { 615 var input = []byte( 616 `{ 617 "apiVersion": "v1", 618 "kind": "Pod", 619 "spec": { 620 "containers": [ 621 { 622 "image": "radial/busyboxplus:curl", 623 "name": "fake0" 624 }, 625 { 626 "image": "radial/busyboxplus:curl", 627 "name": "fake1" 628 }, 629 { 630 "image": "radial/busyboxplus:curl", 631 "name": "fake2" 632 }, 633 { 634 "image": "radial/busyboxplus:curl", 635 "name": "fake3" 636 }]}}`) 637 638 var data interface{} 639 err := json.Unmarshal(input, &data) 640 if err != nil { 641 t.Fatal(err) 642 } 643 644 testJSONPath( 645 []jsonpathTest{ 646 { 647 "test containers[0], it equals containers[0]", 648 `{.spec.containers[0].name}`, 649 data, 650 "fake0", 651 false, 652 }, 653 { 654 "test containers[0:0], it equals the empty set", 655 `{.spec.containers[0:0].name}`, 656 data, 657 "", 658 false, 659 }, 660 { 661 "test containers[0:-1], it equals containers[0:3]", 662 `{.spec.containers[0:-1].name}`, 663 data, 664 "fake0 fake1 fake2", 665 false, 666 }, 667 { 668 "test containers[-1:0], expect error", 669 `{.spec.containers[-1:0].name}`, 670 data, 671 "", 672 true, 673 }, 674 { 675 "test containers[-1], it equals containers[3]", 676 `{.spec.containers[-1].name}`, 677 data, 678 "fake3", 679 false, 680 }, 681 { 682 "test containers[-1:], it equals containers[3:]", 683 `{.spec.containers[-1:].name}`, 684 data, 685 "fake3", 686 false, 687 }, 688 { 689 "test containers[-2], it equals containers[2]", 690 `{.spec.containers[-2].name}`, 691 data, 692 "fake2", 693 false, 694 }, 695 { 696 "test containers[-2:], it equals containers[2:]", 697 `{.spec.containers[-2:].name}`, 698 data, 699 "fake2 fake3", 700 false, 701 }, 702 { 703 "test containers[-3], it equals containers[1]", 704 `{.spec.containers[-3].name}`, 705 data, 706 "fake1", 707 false, 708 }, 709 { 710 "test containers[-4], it equals containers[0]", 711 `{.spec.containers[-4].name}`, 712 data, 713 "fake0", 714 false, 715 }, 716 { 717 "test containers[-4:], it equals containers[0:]", 718 `{.spec.containers[-4:].name}`, 719 data, 720 "fake0 fake1 fake2 fake3", 721 false, 722 }, 723 { 724 "test containers[-5], expect a error cause it out of bounds", 725 `{.spec.containers[-5].name}`, 726 data, 727 "", 728 true, // expect error 729 }, 730 { 731 "test containers[5:5], expect empty set", 732 `{.spec.containers[5:5].name}`, 733 data, 734 "", 735 false, 736 }, 737 { 738 "test containers[-5:-5], expect empty set", 739 `{.spec.containers[-5:-5].name}`, 740 data, 741 "", 742 false, 743 }, 744 { 745 "test containers[3:1], expect a error cause start index is greater than end index", 746 `{.spec.containers[3:1].name}`, 747 data, 748 "", 749 true, 750 }, 751 { 752 "test containers[-1:-2], it equals containers[3:2], expect a error cause start index is greater than end index", 753 `{.spec.containers[-1:-2].name}`, 754 data, 755 "", 756 true, 757 }, 758 }, 759 false, 760 t, 761 ) 762 } 763 764 func TestRunningPodsJSONPathOutput(t *testing.T) { 765 var input = []byte(`{ 766 "kind": "List", 767 "items": [ 768 { 769 "kind": "Pod", 770 "metadata": { 771 "name": "pod1" 772 }, 773 "status": { 774 "phase": "Running" 775 } 776 }, 777 { 778 "kind": "Pod", 779 "metadata": { 780 "name": "pod2" 781 }, 782 "status": { 783 "phase": "Running" 784 } 785 }, 786 { 787 "kind": "Pod", 788 "metadata": { 789 "name": "pod3" 790 }, 791 "status": { 792 "phase": "Running" 793 } 794 }, 795 { 796 "resourceVersion": "", 797 "selfLink": "" 798 } 799 ] 800 }`) 801 var data interface{} 802 err := json.Unmarshal(input, &data) 803 if err != nil { 804 t.Fatal(err) 805 } 806 807 testJSONPath( 808 []jsonpathTest{ 809 { 810 "range over pods without selecting the last one", 811 `{range .items[?(.status.phase=="Running")]}{.metadata.name}{" is Running\n"}{end}`, 812 data, 813 "pod1 is Running\npod2 is Running\npod3 is Running\n", 814 false, // expect no error 815 }, 816 }, 817 true, // allow missing keys 818 t, 819 ) 820 } 821 822 func TestStep(t *testing.T) { 823 var input = []byte( 824 `{ 825 "apiVersion": "v1", 826 "kind": "Pod", 827 "spec": { 828 "containers": [ 829 { 830 "image": "radial/busyboxplus:curl", 831 "name": "fake0" 832 }, 833 { 834 "image": "radial/busyboxplus:curl", 835 "name": "fake1" 836 }, 837 { 838 "image": "radial/busyboxplus:curl", 839 "name": "fake2" 840 }, 841 { 842 "image": "radial/busyboxplus:curl", 843 "name": "fake3" 844 }, 845 { 846 "image": "radial/busyboxplus:curl", 847 "name": "fake4" 848 }, 849 { 850 "image": "radial/busyboxplus:curl", 851 "name": "fake5" 852 }]}}`) 853 854 var data interface{} 855 err := json.Unmarshal(input, &data) 856 if err != nil { 857 t.Fatal(err) 858 } 859 860 testJSONPath( 861 []jsonpathTest{ 862 { 863 "test containers[0:], it equals containers[0:6:1]", 864 `{.spec.containers[0:].name}`, 865 data, 866 "fake0 fake1 fake2 fake3 fake4 fake5", 867 false, 868 }, 869 { 870 "test containers[0:6:], it equals containers[0:6:1]", 871 `{.spec.containers[0:6:].name}`, 872 data, 873 "fake0 fake1 fake2 fake3 fake4 fake5", 874 false, 875 }, 876 { 877 "test containers[0:6:1]", 878 `{.spec.containers[0:6:1].name}`, 879 data, 880 "fake0 fake1 fake2 fake3 fake4 fake5", 881 false, 882 }, 883 { 884 "test containers[0:6:0], it errors", 885 `{.spec.containers[0:6:0].name}`, 886 data, 887 "", 888 true, 889 }, 890 { 891 "test containers[0:6:-1], it errors", 892 `{.spec.containers[0:6:-1].name}`, 893 data, 894 "", 895 true, 896 }, 897 { 898 "test containers[1:4:2]", 899 `{.spec.containers[1:4:2].name}`, 900 data, 901 "fake1 fake3", 902 false, 903 }, 904 { 905 "test containers[1:4:3]", 906 `{.spec.containers[1:4:3].name}`, 907 data, 908 "fake1", 909 false, 910 }, 911 { 912 "test containers[1:4:4]", 913 `{.spec.containers[1:4:4].name}`, 914 data, 915 "fake1", 916 false, 917 }, 918 { 919 "test containers[0:6:2]", 920 `{.spec.containers[0:6:2].name}`, 921 data, 922 "fake0 fake2 fake4", 923 false, 924 }, 925 { 926 "test containers[0:6:3]", 927 `{.spec.containers[0:6:3].name}`, 928 data, 929 "fake0 fake3", 930 false, 931 }, 932 { 933 "test containers[0:6:5]", 934 `{.spec.containers[0:6:5].name}`, 935 data, 936 "fake0 fake5", 937 false, 938 }, 939 { 940 "test containers[0:6:6]", 941 `{.spec.containers[0:6:6].name}`, 942 data, 943 "fake0", 944 false, 945 }, 946 }, 947 false, 948 t, 949 ) 950 }