github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/jsonpath/parser_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 "testing" 21 ) 22 23 type parserTest struct { 24 name string 25 text string 26 nodes []Node 27 shouldError bool 28 } 29 30 var parserTests = []parserTest{ 31 {"plain", `hello jsonpath`, []Node{newText("hello jsonpath")}, false}, 32 {"variable", `hello {.jsonpath}`, 33 []Node{newText("hello "), newList(), newField("jsonpath")}, false}, 34 {"arrayfiled", `hello {['jsonpath']}`, 35 []Node{newText("hello "), newList(), newField("jsonpath")}, false}, 36 {"quote", `{"{"}`, []Node{newList(), newText("{")}, false}, 37 {"array", `{[1:3]}`, []Node{newList(), 38 newArray([3]ParamsEntry{{1, true, false}, {3, true, false}, {0, false, false}})}, false}, 39 {"allarray", `{.book[*].author}`, 40 []Node{newList(), newField("book"), 41 newArray([3]ParamsEntry{{0, false, false}, {0, false, false}, {0, false, false}}), newField("author")}, false}, 42 {"wildcard", `{.bicycle.*}`, 43 []Node{newList(), newField("bicycle"), newWildcard()}, false}, 44 {"filter", `{[?(@.price<3)]}`, 45 []Node{newList(), newFilter(newList(), newList(), "<"), 46 newList(), newField("price"), newList(), newInt(3)}, false}, 47 {"recursive", `{..}`, []Node{newList(), newRecursive()}, false}, 48 {"recurField", `{..price}`, 49 []Node{newList(), newRecursive(), newField("price")}, false}, 50 {"arraydict", `{['book.price']}`, []Node{newList(), 51 newField("book"), newField("price"), 52 }, false}, 53 {"union", `{['bicycle.price', 3, 'book.price']}`, []Node{newList(), newUnion([]*ListNode{}), 54 newList(), newField("bicycle"), newField("price"), 55 newList(), newArray([3]ParamsEntry{{3, true, false}, {4, true, true}, {0, false, false}}), 56 newList(), newField("book"), newField("price"), 57 }, false}, 58 {"range", `{range .items}{.name},{end}`, []Node{ 59 newList(), newIdentifier("range"), newField("items"), 60 newList(), newField("name"), newText(","), 61 newList(), newIdentifier("end"), 62 }, false}, 63 {"malformat input", `{\\\}`, []Node{}, true}, 64 {"paired parentheses in quotes", `{[?(@.status.nodeInfo.osImage == "()")]}`, 65 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("()")}, false}, 66 {"paired parentheses in double quotes and with double quotes escape", `{[?(@.status.nodeInfo.osImage == "(\"\")")]}`, 67 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("(\"\")")}, false}, 68 {"unregular parentheses in double quotes", `{[?(@.test == "())(")]}`, 69 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("test"), newList(), newText("())(")}, false}, 70 {"plain text in single quotes", `{[?(@.status.nodeInfo.osImage == 'Linux')]}`, 71 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("Linux")}, false}, 72 {"test filter suffix", `{[?(@.status.nodeInfo.osImage == "{[()]}")]}`, 73 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("{[()]}")}, false}, 74 {"double inside single", `{[?(@.status.nodeInfo.osImage == "''")]}`, 75 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("''")}, false}, 76 {"single inside double", `{[?(@.status.nodeInfo.osImage == '""')]}`, 77 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\"\"")}, false}, 78 {"single containing escaped single", `{[?(@.status.nodeInfo.osImage == '\\\'')]}`, 79 []Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\\'")}, false}, 80 {"negative index slice, equals a[len-5] to a[len-1]", `{[-5:]}`, []Node{newList(), 81 newArray([3]ParamsEntry{{-5, true, false}, {0, false, false}, {0, false, false}})}, false}, 82 {"negative index slice, equals a[len-1]", `{[-1]}`, []Node{newList(), 83 newArray([3]ParamsEntry{{-1, true, false}, {0, true, true}, {0, false, false}})}, false}, 84 {"negative index slice, equals a[1] to a[len-1]", `{[1:-1]}`, []Node{newList(), 85 newArray([3]ParamsEntry{{1, true, false}, {-1, true, false}, {0, false, false}})}, false}, 86 } 87 88 func collectNode(nodes []Node, cur Node) []Node { 89 nodes = append(nodes, cur) 90 switch cur.Type() { 91 case NodeList: 92 for _, node := range cur.(*ListNode).Nodes { 93 nodes = collectNode(nodes, node) 94 } 95 case NodeFilter: 96 nodes = collectNode(nodes, cur.(*FilterNode).Left) 97 nodes = collectNode(nodes, cur.(*FilterNode).Right) 98 case NodeUnion: 99 for _, node := range cur.(*UnionNode).Nodes { 100 nodes = collectNode(nodes, node) 101 } 102 } 103 return nodes 104 } 105 106 func TestParser(t *testing.T) { 107 for _, test := range parserTests { 108 parser, err := Parse(test.name, test.text) 109 if test.shouldError { 110 if err == nil { 111 t.Errorf("unexpected non-error when parsing %s", test.name) 112 } 113 continue 114 } 115 if err != nil { 116 t.Errorf("parse %s error %v", test.name, err) 117 } 118 result := collectNode([]Node{}, parser.Root)[1:] 119 if len(result) != len(test.nodes) { 120 t.Errorf("in %s, expect to get %d nodes, got %d nodes", test.name, len(test.nodes), len(result)) 121 t.Error(result) 122 } 123 for i, expect := range test.nodes { 124 if result[i].String() != expect.String() { 125 t.Errorf("in %s, %dth node, expect %v, got %v", test.name, i, expect, result[i]) 126 } 127 } 128 } 129 } 130 131 type failParserTest struct { 132 name string 133 text string 134 err string 135 } 136 137 func TestFailParser(t *testing.T) { 138 failParserTests := []failParserTest{ 139 {"unclosed action", "{.hello", "unclosed action"}, 140 {"unrecognized character", "{*}", "unrecognized character in action: U+002A '*'"}, 141 {"invalid number", "{+12.3.0}", "cannot parse number +12.3.0"}, 142 {"unterminated array", "{[1}", "unterminated array"}, 143 {"unterminated filter", "{[?(.price]}", "unterminated filter"}, 144 } 145 for _, test := range failParserTests { 146 _, err := Parse(test.name, test.text) 147 var out string 148 if err == nil { 149 out = "nil" 150 } else { 151 out = err.Error() 152 } 153 if out != test.err { 154 t.Errorf("in %s, expect to get error %v, got %v", test.name, test.err, out) 155 } 156 } 157 }