a60e466129
* Fix index and slice expressions for query Support negative step for slice expressions
614 lines
14 KiB
Go
614 lines
14 KiB
Go
package query
|
||
|
||
import (
|
||
"fmt"
|
||
"io/ioutil"
|
||
"sort"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/pelletier/go-toml"
|
||
)
|
||
|
||
type queryTestNode struct {
|
||
value interface{}
|
||
position toml.Position
|
||
}
|
||
|
||
func valueString(root interface{}) string {
|
||
result := "" //fmt.Sprintf("%T:", root)
|
||
switch node := root.(type) {
|
||
case *Result:
|
||
items := []string{}
|
||
for i, v := range node.Values() {
|
||
items = append(items, fmt.Sprintf("%s:%s",
|
||
node.Positions()[i].String(), valueString(v)))
|
||
}
|
||
sort.Strings(items)
|
||
result = "[" + strings.Join(items, ", ") + "]"
|
||
case queryTestNode:
|
||
result = fmt.Sprintf("%s:%s",
|
||
node.position.String(), valueString(node.value))
|
||
case []interface{}:
|
||
items := []string{}
|
||
for _, v := range node {
|
||
items = append(items, valueString(v))
|
||
}
|
||
sort.Strings(items)
|
||
result = "[" + strings.Join(items, ", ") + "]"
|
||
case *toml.Tree:
|
||
// workaround for unreliable map key ordering
|
||
items := []string{}
|
||
for _, k := range node.Keys() {
|
||
v := node.GetPath([]string{k})
|
||
items = append(items, k+":"+valueString(v))
|
||
}
|
||
sort.Strings(items)
|
||
result = "{" + strings.Join(items, ", ") + "}"
|
||
case map[string]interface{}:
|
||
// workaround for unreliable map key ordering
|
||
items := []string{}
|
||
for k, v := range node {
|
||
items = append(items, k+":"+valueString(v))
|
||
}
|
||
sort.Strings(items)
|
||
result = "{" + strings.Join(items, ", ") + "}"
|
||
case int64:
|
||
result += fmt.Sprintf("%d", node)
|
||
case string:
|
||
result += "'" + node + "'"
|
||
case float64:
|
||
result += fmt.Sprintf("%f", node)
|
||
case bool:
|
||
result += fmt.Sprintf("%t", node)
|
||
case time.Time:
|
||
result += fmt.Sprintf("'%v'", node)
|
||
}
|
||
return result
|
||
}
|
||
|
||
func assertValue(t *testing.T, result, ref interface{}) {
|
||
pathStr := valueString(result)
|
||
refStr := valueString(ref)
|
||
if pathStr != refStr {
|
||
t.Errorf("values do not match")
|
||
t.Log("test:", pathStr)
|
||
t.Log("ref: ", refStr)
|
||
}
|
||
}
|
||
|
||
func assertParseError(t *testing.T, query string, errString string) {
|
||
_, err := Compile(query)
|
||
if err == nil {
|
||
t.Error("error should be non-nil")
|
||
return
|
||
}
|
||
if err.Error() != errString {
|
||
t.Errorf("error does not match")
|
||
t.Log("test:", err.Error())
|
||
t.Log("ref: ", errString)
|
||
}
|
||
}
|
||
|
||
func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
|
||
tree, err := toml.Load(tomlDoc)
|
||
if err != nil {
|
||
t.Errorf("Non-nil toml parse error: %v", err)
|
||
return
|
||
}
|
||
q, err := Compile(query)
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
results := q.Execute(tree)
|
||
assertValue(t, results, ref)
|
||
}
|
||
|
||
func TestQueryRoot(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"a = 42",
|
||
"$",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(42),
|
||
}, toml.Position{1, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryKey(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = 42",
|
||
"$.foo.a",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
int64(42), toml.Position{2, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryKeyString(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = 42",
|
||
"$.foo['a']",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
int64(42), toml.Position{2, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryKeyUnicodeString(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"['f𝟘.o']\na = 42",
|
||
"$['f𝟘.o']['a']",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
int64(42), toml.Position{2, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryIndexError1(t *testing.T) {
|
||
assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''")
|
||
}
|
||
|
||
func TestQueryIndexError2(t *testing.T) {
|
||
assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0")
|
||
}
|
||
|
||
func TestQueryIndex(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[5]",
|
||
[]interface{}{
|
||
queryTestNode{int64(5), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQueryIndexNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[-2]",
|
||
[]interface{}{
|
||
queryTestNode{int64(8), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQueryIndexWrong(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[99]",
|
||
[]interface{}{})
|
||
}
|
||
|
||
func TestQueryIndexEmpty(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = []",
|
||
"$.foo.a[5]",
|
||
[]interface{}{})
|
||
}
|
||
|
||
func TestQueryIndexTree(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3",
|
||
"$.foo[1].b",
|
||
[]interface{}{
|
||
queryTestNode{int64(3), toml.Position{4, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceError1(t *testing.T) {
|
||
assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'")
|
||
}
|
||
|
||
func TestQuerySliceError2(t *testing.T) {
|
||
assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'")
|
||
}
|
||
|
||
func TestQuerySliceError3(t *testing.T) {
|
||
assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero")
|
||
}
|
||
|
||
func TestQuerySliceRange(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[:5]",
|
||
[]interface{}{
|
||
queryTestNode{int64(0), toml.Position{2, 1}},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
queryTestNode{int64(2), toml.Position{2, 1}},
|
||
queryTestNode{int64(3), toml.Position{2, 1}},
|
||
queryTestNode{int64(4), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceStep(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[0:5:2]",
|
||
[]interface{}{
|
||
queryTestNode{int64(0), toml.Position{2, 1}},
|
||
queryTestNode{int64(2), toml.Position{2, 1}},
|
||
queryTestNode{int64(4), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceStartNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[-3:]",
|
||
[]interface{}{
|
||
queryTestNode{int64(7), toml.Position{2, 1}},
|
||
queryTestNode{int64(8), toml.Position{2, 1}},
|
||
queryTestNode{int64(9), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceEndNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[:-6]",
|
||
[]interface{}{
|
||
queryTestNode{int64(0), toml.Position{2, 1}},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
queryTestNode{int64(2), toml.Position{2, 1}},
|
||
queryTestNode{int64(3), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceStepNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[::-2]",
|
||
[]interface{}{
|
||
queryTestNode{int64(9), toml.Position{2, 1}},
|
||
queryTestNode{int64(7), toml.Position{2, 1}},
|
||
queryTestNode{int64(5), toml.Position{2, 1}},
|
||
queryTestNode{int64(3), toml.Position{2, 1}},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceStartOverRange(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[-99:3]",
|
||
[]interface{}{
|
||
queryTestNode{int64(0), toml.Position{2, 1}},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
queryTestNode{int64(2), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceStartOverRangeNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[99:7:-1]",
|
||
[]interface{}{
|
||
queryTestNode{int64(9), toml.Position{2, 1}},
|
||
queryTestNode{int64(8), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceEndOverRange(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[7:99]",
|
||
[]interface{}{
|
||
queryTestNode{int64(7), toml.Position{2, 1}},
|
||
queryTestNode{int64(8), toml.Position{2, 1}},
|
||
queryTestNode{int64(9), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceEndOverRangeNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[2:-99:-1]",
|
||
[]interface{}{
|
||
queryTestNode{int64(2), toml.Position{2, 1}},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
queryTestNode{int64(0), toml.Position{2, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQuerySliceWrongRange(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[5:3]",
|
||
[]interface{}{})
|
||
}
|
||
|
||
func TestQuerySliceWrongRangeNegative(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
||
"$.foo.a[3:5:-1]",
|
||
[]interface{}{})
|
||
}
|
||
|
||
func TestQuerySliceEmpty(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo]\na = []",
|
||
"$.foo.a[5:]",
|
||
[]interface{}{})
|
||
}
|
||
|
||
func TestQuerySliceTree(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3",
|
||
"$.foo[1:].a",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
[]interface{}{
|
||
int64(0), int64(1), int64(2), int64(3), int64(4),
|
||
int64(5), int64(6), int64(7), int64(8), int64(9)},
|
||
toml.Position{4, 1}},
|
||
queryTestNode{"ok", toml.Position{6, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQueryAny(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
||
"$.foo.*",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
}, toml.Position{4, 1},
|
||
},
|
||
})
|
||
}
|
||
func TestQueryUnionSimple(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||
"$.*[bar,foo]",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
}, toml.Position{4, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(5),
|
||
"b": int64(6),
|
||
}, toml.Position{7, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryRecursionAll(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||
"$..*",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"foo": map[string]interface{}{
|
||
"bar": map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
},
|
||
},
|
||
"baz": map[string]interface{}{
|
||
"foo": map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
},
|
||
},
|
||
"gorf": map[string]interface{}{
|
||
"foo": map[string]interface{}{
|
||
"a": int64(5),
|
||
"b": int64(6),
|
||
},
|
||
},
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"bar": map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
},
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{int64(1), toml.Position{2, 1}},
|
||
queryTestNode{int64(2), toml.Position{3, 1}},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"foo": map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
},
|
||
}, toml.Position{4, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
}, toml.Position{4, 1},
|
||
},
|
||
queryTestNode{int64(3), toml.Position{5, 1}},
|
||
queryTestNode{int64(4), toml.Position{6, 1}},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"foo": map[string]interface{}{
|
||
"a": int64(5),
|
||
"b": int64(6),
|
||
},
|
||
}, toml.Position{7, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(5),
|
||
"b": int64(6),
|
||
}, toml.Position{7, 1},
|
||
},
|
||
queryTestNode{int64(5), toml.Position{8, 1}},
|
||
queryTestNode{int64(6), toml.Position{9, 1}},
|
||
})
|
||
}
|
||
|
||
func TestQueryRecursionUnionSimple(t *testing.T) {
|
||
assertQueryPositions(t,
|
||
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
||
"$..['foo','bar']",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"bar": map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
},
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(3),
|
||
"b": int64(4),
|
||
}, toml.Position{4, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(1),
|
||
"b": int64(2),
|
||
}, toml.Position{1, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"a": int64(5),
|
||
"b": int64(6),
|
||
}, toml.Position{7, 1},
|
||
},
|
||
})
|
||
}
|
||
|
||
func TestQueryFilterFn(t *testing.T) {
|
||
buff, err := ioutil.ReadFile("../example.toml")
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(int)]",
|
||
[]interface{}{
|
||
queryTestNode{int64(8001), toml.Position{13, 1}},
|
||
queryTestNode{int64(8001), toml.Position{13, 1}},
|
||
queryTestNode{int64(8002), toml.Position{13, 1}},
|
||
queryTestNode{int64(5000), toml.Position{14, 1}},
|
||
})
|
||
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(string)]",
|
||
[]interface{}{
|
||
queryTestNode{"TOML Example", toml.Position{3, 1}},
|
||
queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}},
|
||
queryTestNode{"GitHub", toml.Position{7, 1}},
|
||
queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}},
|
||
queryTestNode{"192.168.1.1", toml.Position{12, 1}},
|
||
queryTestNode{"10.0.0.1", toml.Position{21, 3}},
|
||
queryTestNode{"eqdc10", toml.Position{22, 3}},
|
||
queryTestNode{"10.0.0.2", toml.Position{25, 3}},
|
||
queryTestNode{"eqdc10", toml.Position{26, 3}},
|
||
})
|
||
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(float)]",
|
||
[]interface{}{
|
||
queryTestNode{4e-08, toml.Position{30, 1}},
|
||
})
|
||
|
||
tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(tree)]",
|
||
[]interface{}{
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"name": "Tom Preston-Werner",
|
||
"organization": "GitHub",
|
||
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||
"dob": tv,
|
||
}, toml.Position{5, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"server": "192.168.1.1",
|
||
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
|
||
"connection_max": int64(5000),
|
||
"enabled": true,
|
||
}, toml.Position{11, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"alpha": map[string]interface{}{
|
||
"ip": "10.0.0.1",
|
||
"dc": "eqdc10",
|
||
},
|
||
"beta": map[string]interface{}{
|
||
"ip": "10.0.0.2",
|
||
"dc": "eqdc10",
|
||
},
|
||
}, toml.Position{17, 1},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"ip": "10.0.0.1",
|
||
"dc": "eqdc10",
|
||
}, toml.Position{20, 3},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"ip": "10.0.0.2",
|
||
"dc": "eqdc10",
|
||
}, toml.Position{24, 3},
|
||
},
|
||
queryTestNode{
|
||
map[string]interface{}{
|
||
"data": []interface{}{
|
||
[]interface{}{"gamma", "delta"},
|
||
[]interface{}{int64(1), int64(2)},
|
||
},
|
||
"score": 4e-08,
|
||
}, toml.Position{28, 1},
|
||
},
|
||
})
|
||
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(time)]",
|
||
[]interface{}{
|
||
queryTestNode{tv, toml.Position{9, 1}},
|
||
})
|
||
|
||
assertQueryPositions(t, string(buff),
|
||
"$..[?(bool)]",
|
||
[]interface{}{
|
||
queryTestNode{true, toml.Position{15, 1}},
|
||
})
|
||
}
|