...

Source file src/k8s.io/client-go/util/jsonpath/jsonpath_test.go

Documentation: k8s.io/client-go/util/jsonpath

     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  		{"id": "i7", "x":  null, "y":  4 }
   306  	]`)
   307  	var pointsData interface{}
   308  	err := json.Unmarshal(pointsJSON, &pointsData)
   309  	if err != nil {
   310  		t.Error(err)
   311  	}
   312  	pointsTests := []jsonpathTest{
   313  		{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5", false},
   314  		{"bracket key", "{[0]['id']}", pointsData, "i1", false},
   315  		{"nil value", "{[-1]['x']}", pointsData, "null", false},
   316  	}
   317  	testJSONPath(pointsTests, false, t)
   318  }
   319  
   320  // TestKubernetes tests some use cases from kubernetes
   321  func TestKubernetes(t *testing.T) {
   322  	var input = []byte(`{
   323  	  "kind": "List",
   324  	  "items":[
   325  		{
   326  		  "kind":"None",
   327  		  "metadata":{
   328  		    "name":"127.0.0.1",
   329  			"labels":{
   330  			  "kubernetes.io/hostname":"127.0.0.1"
   331  			}
   332  		  },
   333  		  "status":{
   334  			"capacity":{"cpu":"4"},
   335  			"ready": true,
   336  			"addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}]
   337  		  }
   338  		},
   339  		{
   340  		  "kind":"None",
   341  		  "metadata":{
   342  			"name":"127.0.0.2",
   343  			"labels":{
   344  			  "kubernetes.io/hostname":"127.0.0.2"
   345  			}
   346  		  },
   347  		  "status":{
   348  			"capacity":{"cpu":"8"},
   349  			"ready": false,
   350  			"addresses":[
   351  			  {"type": "LegacyHostIP", "address":"127.0.0.2"},
   352  			  {"type": "another", "address":"127.0.0.3"}
   353  			]
   354  		  }
   355  		}
   356  	  ],
   357  	  "users":[
   358  	    {
   359  	      "name": "myself",
   360  	      "user": {}
   361  	    },
   362  	    {
   363  	      "name": "e2e",
   364  	      "user": {"username": "admin", "password": "secret"}
   365  	  	}
   366  	  ]
   367  	}`)
   368  	var nodesData interface{}
   369  	err := json.Unmarshal(input, &nodesData)
   370  	if err != nil {
   371  		t.Error(err)
   372  	}
   373  
   374  	nodesTests := []jsonpathTest{
   375  		{"range item", `{range .items[*]}{.metadata.name}, {end}{.kind}`, nodesData, "127.0.0.1, 127.0.0.2, List", false},
   376  		{"range item with quote", `{range .items[*]}{.metadata.name}{"\t"}{end}`, nodesData, "127.0.0.1\t127.0.0.2\t", false},
   377  		{"range addresss", `{.items[*].status.addresses[*].address}`, nodesData,
   378  			"127.0.0.1 127.0.0.2 127.0.0.3", false},
   379  		{"double range", `{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}`, nodesData,
   380  			"127.0.0.1, 127.0.0.2, 127.0.0.3, ", false},
   381  		{"item name", `{.items[*].metadata.name}`, nodesData, "127.0.0.1 127.0.0.2", false},
   382  		{"union nodes capacity", `{.items[*]['metadata.name', 'status.capacity']}`, nodesData,
   383  			`127.0.0.1 127.0.0.2 {"cpu":"4"} {"cpu":"8"}`, false},
   384  		{"range nodes capacity", `{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}`, nodesData,
   385  			`[127.0.0.1, {"cpu":"4"}] [127.0.0.2, {"cpu":"8"}] `, false},
   386  		{"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret", false},
   387  		{"hostname", `{.items[0].metadata.labels.kubernetes\.io/hostname}`, &nodesData, "127.0.0.1", false},
   388  		{"hostname filter", `{.items[?(@.metadata.labels.kubernetes\.io/hostname=="127.0.0.1")].kind}`, &nodesData, "None", false},
   389  		{"bool item", `{.items[?(@..ready==true)].metadata.name}`, &nodesData, "127.0.0.1", false},
   390  	}
   391  	testJSONPath(nodesTests, false, t)
   392  
   393  	randomPrintOrderTests := []jsonpathTest{
   394  		{"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`, false},
   395  	}
   396  	testJSONPathSortOutput(randomPrintOrderTests, t)
   397  }
   398  
   399  func TestEmptyRange(t *testing.T) {
   400  	var input = []byte(`{"items":[]}`)
   401  	var emptyList interface{}
   402  	err := json.Unmarshal(input, &emptyList)
   403  	if err != nil {
   404  		t.Error(err)
   405  	}
   406  
   407  	tests := []jsonpathTest{
   408  		{"empty range", `{range .items[*]}{.metadata.name}{end}`, &emptyList, "", false},
   409  		{"empty nested range", `{range .items[*]}{.metadata.name}{":"}{range @.spec.containers[*]}{.name}{","}{end}{"+"}{end}`, &emptyList, "", false},
   410  	}
   411  	testJSONPath(tests, true, t)
   412  }
   413  
   414  func TestNestedRanges(t *testing.T) {
   415  	var input = []byte(`{
   416  		"items": [
   417  			{
   418  				"metadata": {
   419  					"name": "pod1"
   420  				},
   421  				"spec": {
   422  					"containers": [
   423  						{
   424  							"name": "foo",
   425  							"another": [
   426  								{ "name": "value1" },
   427  								{ "name": "value2" }
   428  							]
   429  						},
   430  						{
   431  							"name": "bar",
   432  							"another": [
   433  								{ "name": "value1" },
   434  								{ "name": "value2" }
   435  							]
   436  						}
   437  					]
   438                  }
   439  			},
   440  			{
   441  				"metadata": {
   442  					"name": "pod2"
   443  				},
   444  				"spec": {
   445  					"containers": [
   446  						{
   447  							"name": "baz",
   448  							"another": [
   449  								{ "name": "value1" },
   450  								{ "name": "value2" }
   451  							]
   452  						}
   453  					]
   454                  }
   455  			}
   456  		]
   457  	}`)
   458  	var data interface{}
   459  	err := json.Unmarshal(input, &data)
   460  	if err != nil {
   461  		t.Error(err)
   462  	}
   463  
   464  	testJSONPath(
   465  		[]jsonpathTest{
   466  			{
   467  				"nested range with a trailing newline",
   468  				`{range .items[*]}` +
   469  					`{.metadata.name}` +
   470  					`{":"}` +
   471  					`{range @.spec.containers[*]}` +
   472  					`{.name}` +
   473  					`{","}` +
   474  					`{end}` +
   475  					`{"+"}` +
   476  					`{end}`,
   477  				data,
   478  				"pod1:foo,bar,+pod2:baz,+",
   479  				false,
   480  			},
   481  		},
   482  		false,
   483  		t,
   484  	)
   485  
   486  	testJSONPath(
   487  		[]jsonpathTest{
   488  			{
   489  				"nested range with a trailing character within another nested range with a trailing newline",
   490  				`{range .items[*]}` +
   491  					`{.metadata.name}` +
   492  					`{"~"}` +
   493  					`{range @.spec.containers[*]}` +
   494  					`{.name}` +
   495  					`{":"}` +
   496  					`{range @.another[*]}` +
   497  					`{.name}` +
   498  					`{","}` +
   499  					`{end}` +
   500  					`{"+"}` +
   501  					`{end}` +
   502  					`{"#"}` +
   503  					`{end}`,
   504  				data,
   505  				"pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#",
   506  				false,
   507  			},
   508  		},
   509  		false,
   510  		t,
   511  	)
   512  
   513  	testJSONPath(
   514  		[]jsonpathTest{
   515  			{
   516  				"two nested ranges at the same level with a trailing newline",
   517  				`{range .items[*]}` +
   518  					`{.metadata.name}` +
   519  					`{"\t"}` +
   520  					`{range @.spec.containers[*]}` +
   521  					`{.name}` +
   522  					`{" "}` +
   523  					`{end}` +
   524  					`{"\t"}` +
   525  					`{range @.spec.containers[*]}` +
   526  					`{.name}` +
   527  					`{" "}` +
   528  					`{end}` +
   529  					`{"\n"}` +
   530  					`{end}`,
   531  				data,
   532  				"pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n",
   533  				false,
   534  			},
   535  		},
   536  		false,
   537  		t,
   538  	)
   539  }
   540  
   541  func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) {
   542  	// for https://issues.k8s.io/45546
   543  	var input = []byte(`{
   544  		"kind": "List",
   545  		"items": [
   546  			{
   547  				"kind": "Pod",
   548  				"metadata": {
   549  					"name": "pod1",
   550  					"annotations": {
   551  						"color": "blue"
   552  					}
   553  				}
   554  			},
   555  			{
   556  				"kind": "Pod",
   557  				"metadata": {
   558  					"name": "pod2"
   559  				}
   560  			},
   561  			{
   562  				"kind": "Pod",
   563  				"metadata": {
   564  					"name": "pod3",
   565  					"annotations": {
   566  						"color": "green"
   567  					}
   568  				}
   569  			},
   570  			{
   571  				"kind": "Pod",
   572  				"metadata": {
   573  					"name": "pod4",
   574  					"annotations": {
   575  						"color": "blue"
   576  					}
   577  				}
   578  			}
   579  		]
   580  	}`)
   581  	var data interface{}
   582  	err := json.Unmarshal(input, &data)
   583  	if err != nil {
   584  		t.Fatal(err)
   585  	}
   586  
   587  	testJSONPath(
   588  		[]jsonpathTest{
   589  			{
   590  				"filter, should only match a subset, some items don't have annotations, tolerate missing items",
   591  				`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
   592  				data,
   593  				"pod1 pod4",
   594  				false, // expect no error
   595  			},
   596  		},
   597  		true, // allow missing keys
   598  		t,
   599  	)
   600  
   601  	testJSONPath(
   602  		[]jsonpathTest{
   603  			{
   604  				"filter, should only match a subset, some items don't have annotations, error on missing items",
   605  				`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
   606  				data,
   607  				"",
   608  				true, // expect an error
   609  			},
   610  		},
   611  		false, // don't allow missing keys
   612  		t,
   613  	)
   614  }
   615  
   616  func TestNegativeIndex(t *testing.T) {
   617  	var input = []byte(
   618  		`{
   619  			"apiVersion": "v1",
   620  			"kind": "Pod",
   621  			"spec": {
   622  				"containers": [
   623  					{
   624  						"image": "radial/busyboxplus:curl",
   625  						"name": "fake0"
   626  					},
   627  					{
   628  						"image": "radial/busyboxplus:curl",
   629  						"name": "fake1"
   630  					},
   631  					{
   632  						"image": "radial/busyboxplus:curl",
   633  						"name": "fake2"
   634  					},
   635  					{
   636  						"image": "radial/busyboxplus:curl",
   637  						"name": "fake3"
   638  					}]}}`)
   639  
   640  	var data interface{}
   641  	err := json.Unmarshal(input, &data)
   642  	if err != nil {
   643  		t.Fatal(err)
   644  	}
   645  
   646  	testJSONPath(
   647  		[]jsonpathTest{
   648  			{
   649  				"test containers[0], it equals containers[0]",
   650  				`{.spec.containers[0].name}`,
   651  				data,
   652  				"fake0",
   653  				false,
   654  			},
   655  			{
   656  				"test containers[0:0], it equals the empty set",
   657  				`{.spec.containers[0:0].name}`,
   658  				data,
   659  				"",
   660  				false,
   661  			},
   662  			{
   663  				"test containers[0:-1], it equals containers[0:3]",
   664  				`{.spec.containers[0:-1].name}`,
   665  				data,
   666  				"fake0 fake1 fake2",
   667  				false,
   668  			},
   669  			{
   670  				"test containers[-1:0], expect error",
   671  				`{.spec.containers[-1:0].name}`,
   672  				data,
   673  				"",
   674  				true,
   675  			},
   676  			{
   677  				"test containers[-1], it equals containers[3]",
   678  				`{.spec.containers[-1].name}`,
   679  				data,
   680  				"fake3",
   681  				false,
   682  			},
   683  			{
   684  				"test containers[-1:], it equals containers[3:]",
   685  				`{.spec.containers[-1:].name}`,
   686  				data,
   687  				"fake3",
   688  				false,
   689  			},
   690  			{
   691  				"test containers[-2], it equals containers[2]",
   692  				`{.spec.containers[-2].name}`,
   693  				data,
   694  				"fake2",
   695  				false,
   696  			},
   697  			{
   698  				"test containers[-2:], it equals containers[2:]",
   699  				`{.spec.containers[-2:].name}`,
   700  				data,
   701  				"fake2 fake3",
   702  				false,
   703  			},
   704  			{
   705  				"test containers[-3], it equals containers[1]",
   706  				`{.spec.containers[-3].name}`,
   707  				data,
   708  				"fake1",
   709  				false,
   710  			},
   711  			{
   712  				"test containers[-4], it equals containers[0]",
   713  				`{.spec.containers[-4].name}`,
   714  				data,
   715  				"fake0",
   716  				false,
   717  			},
   718  			{
   719  				"test containers[-4:], it equals containers[0:]",
   720  				`{.spec.containers[-4:].name}`,
   721  				data,
   722  				"fake0 fake1 fake2 fake3",
   723  				false,
   724  			},
   725  			{
   726  				"test containers[-5], expect a error cause it out of bounds",
   727  				`{.spec.containers[-5].name}`,
   728  				data,
   729  				"",
   730  				true, // expect error
   731  			},
   732  			{
   733  				"test containers[5:5], expect empty set",
   734  				`{.spec.containers[5:5].name}`,
   735  				data,
   736  				"",
   737  				false,
   738  			},
   739  			{
   740  				"test containers[-5:-5], expect empty set",
   741  				`{.spec.containers[-5:-5].name}`,
   742  				data,
   743  				"",
   744  				false,
   745  			},
   746  			{
   747  				"test containers[3:1], expect a error cause start index is greater than end index",
   748  				`{.spec.containers[3:1].name}`,
   749  				data,
   750  				"",
   751  				true,
   752  			},
   753  			{
   754  				"test containers[-1:-2], it equals containers[3:2], expect a error cause start index is greater than end index",
   755  				`{.spec.containers[-1:-2].name}`,
   756  				data,
   757  				"",
   758  				true,
   759  			},
   760  		},
   761  		false,
   762  		t,
   763  	)
   764  }
   765  
   766  func TestRunningPodsJSONPathOutput(t *testing.T) {
   767  	var input = []byte(`{
   768  		"kind": "List",
   769  		"items": [
   770  			{
   771  				"kind": "Pod",
   772  				"metadata": {
   773  					"name": "pod1"
   774  				},
   775  				"status": {
   776  						"phase": "Running"
   777  				}
   778  			},
   779  			{
   780  				"kind": "Pod",
   781  				"metadata": {
   782  					"name": "pod2"
   783  				},
   784  				"status": {
   785  						"phase": "Running"
   786  				}
   787  			},
   788  			{
   789  				"kind": "Pod",
   790  				"metadata": {
   791  					"name": "pod3"
   792  				},
   793  				"status": {
   794  						"phase": "Running"
   795  				}
   796  			},
   797             		{
   798  				"resourceVersion": ""
   799  			}
   800  		]
   801  	}`)
   802  	var data interface{}
   803  	err := json.Unmarshal(input, &data)
   804  	if err != nil {
   805  		t.Fatal(err)
   806  	}
   807  
   808  	testJSONPath(
   809  		[]jsonpathTest{
   810  			{
   811  				"range over pods without selecting the last one",
   812  				`{range .items[?(.status.phase=="Running")]}{.metadata.name}{" is Running\n"}{end}`,
   813  				data,
   814  				"pod1 is Running\npod2 is Running\npod3 is Running\n",
   815  				false, // expect no error
   816  			},
   817  		},
   818  		true, // allow missing keys
   819  		t,
   820  	)
   821  }
   822  
   823  func TestStep(t *testing.T) {
   824  	var input = []byte(
   825  		`{
   826  			"apiVersion": "v1",
   827  			"kind": "Pod",
   828  			"spec": {
   829  				"containers": [
   830  					{
   831  						"image": "radial/busyboxplus:curl",
   832  						"name": "fake0"
   833  					},
   834  					{
   835  						"image": "radial/busyboxplus:curl",
   836  						"name": "fake1"
   837  					},
   838  					{
   839  						"image": "radial/busyboxplus:curl",
   840  						"name": "fake2"
   841  					},
   842  					{
   843  						"image": "radial/busyboxplus:curl",
   844  						"name": "fake3"
   845  					},
   846  					{
   847  						"image": "radial/busyboxplus:curl",
   848  						"name": "fake4"
   849  					},
   850  					{
   851  						"image": "radial/busyboxplus:curl",
   852  						"name": "fake5"
   853  					}]}}`)
   854  
   855  	var data interface{}
   856  	err := json.Unmarshal(input, &data)
   857  	if err != nil {
   858  		t.Fatal(err)
   859  	}
   860  
   861  	testJSONPath(
   862  		[]jsonpathTest{
   863  			{
   864  				"test containers[0:], it equals containers[0:6:1]",
   865  				`{.spec.containers[0:].name}`,
   866  				data,
   867  				"fake0 fake1 fake2 fake3 fake4 fake5",
   868  				false,
   869  			},
   870  			{
   871  				"test containers[0:6:], it equals containers[0:6:1]",
   872  				`{.spec.containers[0:6:].name}`,
   873  				data,
   874  				"fake0 fake1 fake2 fake3 fake4 fake5",
   875  				false,
   876  			},
   877  			{
   878  				"test containers[0:6:1]",
   879  				`{.spec.containers[0:6:1].name}`,
   880  				data,
   881  				"fake0 fake1 fake2 fake3 fake4 fake5",
   882  				false,
   883  			},
   884  			{
   885  				"test containers[0:6:0], it errors",
   886  				`{.spec.containers[0:6:0].name}`,
   887  				data,
   888  				"",
   889  				true,
   890  			},
   891  			{
   892  				"test containers[0:6:-1], it errors",
   893  				`{.spec.containers[0:6:-1].name}`,
   894  				data,
   895  				"",
   896  				true,
   897  			},
   898  			{
   899  				"test containers[1:4:2]",
   900  				`{.spec.containers[1:4:2].name}`,
   901  				data,
   902  				"fake1 fake3",
   903  				false,
   904  			},
   905  			{
   906  				"test containers[1:4:3]",
   907  				`{.spec.containers[1:4:3].name}`,
   908  				data,
   909  				"fake1",
   910  				false,
   911  			},
   912  			{
   913  				"test containers[1:4:4]",
   914  				`{.spec.containers[1:4:4].name}`,
   915  				data,
   916  				"fake1",
   917  				false,
   918  			},
   919  			{
   920  				"test containers[0:6:2]",
   921  				`{.spec.containers[0:6:2].name}`,
   922  				data,
   923  				"fake0 fake2 fake4",
   924  				false,
   925  			},
   926  			{
   927  				"test containers[0:6:3]",
   928  				`{.spec.containers[0:6:3].name}`,
   929  				data,
   930  				"fake0 fake3",
   931  				false,
   932  			},
   933  			{
   934  				"test containers[0:6:5]",
   935  				`{.spec.containers[0:6:5].name}`,
   936  				data,
   937  				"fake0 fake5",
   938  				false,
   939  			},
   940  			{
   941  				"test containers[0:6:6]",
   942  				`{.spec.containers[0:6:6].name}`,
   943  				data,
   944  				"fake0",
   945  				false,
   946  			},
   947  		},
   948  		false,
   949  		t,
   950  	)
   951  }
   952  

View as plain text