mirror of
https://github.com/grafana/tempo.git
synced 2025-03-14 03:06:42 +00:00
* added sum_over_time to language * rename evaluator * refactor overtime functions * added documentation * fix compensation * fix test due to floating point discrepancy * changelog * fix expr.y * linting * Update docs/sources/tempo/traceql/metrics-queries/_index.md Co-authored-by: Ruslan Mikhailov <195758209+ruslan-mikhailov@users.noreply.github.com> * Update docs/sources/tempo/traceql/metrics-queries/functions.md Co-authored-by: Martin Disibio <mdisibio@gmail.com> * add function --------- Co-authored-by: Ruslan Mikhailov <195758209+ruslan-mikhailov@users.noreply.github.com> Co-authored-by: Martin Disibio <mdisibio@gmail.com>
1445 lines
46 KiB
Go
1445 lines
46 KiB
Go
package traceql
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPipelineErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "", err: newParseError("syntax error: unexpected $end", 0, 0)},
|
|
{in: "{ .a } | { .b", err: newParseError("syntax error: unexpected $end", 1, 14)},
|
|
{in: "{ .a | .b }", err: newParseError("syntax error: unexpected |", 1, 6)},
|
|
{in: "({ .a } | { .b }", err: newParseError("syntax error: unexpected $end, expecting ) or |", 1, 17)},
|
|
{in: "({ .a } | { .b }) + ({ .a } | { .b })", err: newParseError("syntax error: unexpected +, expecting with", 1, 19)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPipelineOperatorPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected SpansetOperation
|
|
}{
|
|
{
|
|
in: "({ .a } | { .b }) > ({ .a } | { .b }) && ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetAnd,
|
|
newSpansetOperation(OpSpansetChild,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) > (({ .a } | { .b }) && ({ .a } | { .b }))",
|
|
expected: newSpansetOperation(OpSpansetChild,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newSpansetOperation(OpSpansetAnd,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) < (({ .a } | { .b }) && ({ .a } | { .b }))",
|
|
expected: newSpansetOperation(OpSpansetParent,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newSpansetOperation(OpSpansetAnd,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPipelineSpansetOperators(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected SpansetOperation
|
|
}{
|
|
{
|
|
in: "({ .a } | { .b }) > ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetChild,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) < ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetParent,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) ~ ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetSibling,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) && ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetAnd,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) >> ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetDescendant,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) << ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetAncestor,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) !> ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetNotChild,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) !< ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetNotParent,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) !~ ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetNotSibling,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) !>> ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetNotDescendant,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) !<< ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetNotAncestor,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) &> ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetUnionChild,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) &< ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetUnionParent,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) &~ ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetUnionSibling,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) &>> ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetUnionDescendant,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | { .b }) &<< ({ .a } | { .b })",
|
|
expected: newSpansetOperation(OpSpansetUnionAncestor,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPipelineScalarOperators(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected ScalarFilter
|
|
}{
|
|
{
|
|
in: "({ .a } | count()) = ({ .a } | count())",
|
|
expected: newScalarFilter(OpEqual,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | count()) != ({ .a } | count())",
|
|
expected: newScalarFilter(OpNotEqual,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | count()) < ({ .a } | count())",
|
|
expected: newScalarFilter(OpLess,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | count()) <= ({ .a } | count())",
|
|
expected: newScalarFilter(OpLessEqual,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "({ .a } | count()) >= ({ .a } | count())",
|
|
expected: newScalarFilter(OpGreaterEqual,
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPipelines(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected Pipeline
|
|
}{
|
|
{
|
|
in: "{ .a } | { .b }",
|
|
expected: newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newSpansetFilter(NewAttribute("b")),
|
|
),
|
|
},
|
|
{
|
|
in: "{ .a } | count() > 1",
|
|
expected: newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newScalarFilter(OpGreater, newAggregate(aggregateCount, nil), NewStaticInt(1)),
|
|
),
|
|
},
|
|
{
|
|
in: "{ .a } | by(.namespace) | coalesce() | avg(duration) = 1s ",
|
|
expected: newPipeline(
|
|
newSpansetFilter(NewAttribute("a")),
|
|
newGroupOperation(NewAttribute("namespace")),
|
|
newCoalesceOperation(),
|
|
newScalarFilter(OpEqual, newAggregate(aggregateAvg, NewIntrinsic(IntrinsicDuration)), NewStaticDuration(time.Second)),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(tc.expected), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupCoalesceErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "by(.a) && { .b }", err: newParseError("syntax error: unexpected &&, expecting with", 0, 8)},
|
|
{in: "by()", err: newParseError("syntax error: unexpected )", 1, 4)},
|
|
{in: "coalesce()", err: newParseError("syntax error: unexpected coalesce", 1, 1)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupCoalesceOperation(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected Pipeline
|
|
}{
|
|
{in: "by(.a) | coalesce()", expected: newPipeline(newGroupOperation(NewAttribute("a")), newCoalesceOperation())},
|
|
{in: "by(.a + .b)", expected: newPipeline(newGroupOperation(newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b"))))},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(tc.expected), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSelectErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "select(.a) && { .b }", err: newParseError("syntax error: unexpected &&, expecting with", 0, 12)},
|
|
{in: "select()", err: newParseError("syntax error: unexpected )", 1, 8)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSelectOperation(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected Pipeline
|
|
}{
|
|
{in: "select(.a)", expected: newPipeline(newSelectOperation([]Attribute{NewAttribute("a")}))},
|
|
{in: "select(.a,.b)", expected: newPipeline(newSelectOperation([]Attribute{NewAttribute("a"), NewAttribute("b")}))},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(tc.expected), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetExpressionErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "{ true } &&", err: newParseError("syntax error: unexpected $end, expecting { or (", 1, 12)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetExpressionPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected SpansetOperation
|
|
}{
|
|
{
|
|
in: "{ true } && { false } >> { `a` }",
|
|
expected: newSpansetOperation(OpSpansetAnd,
|
|
newSpansetFilter(NewStaticBool(true)),
|
|
newSpansetOperation(OpSpansetDescendant, newSpansetFilter(NewStaticBool(false)), newSpansetFilter(NewStaticString("a"))),
|
|
),
|
|
},
|
|
{
|
|
in: "{ true } >> { false } && { `a` }",
|
|
expected: newSpansetOperation(OpSpansetAnd,
|
|
newSpansetOperation(OpSpansetDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false))),
|
|
newSpansetFilter(NewStaticString("a")),
|
|
),
|
|
},
|
|
{
|
|
in: "({ true } >> { false }) && { `a` }",
|
|
expected: newSpansetOperation(OpSpansetAnd,
|
|
newSpansetOperation(OpSpansetDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false))),
|
|
newSpansetFilter(NewStaticString("a")),
|
|
),
|
|
},
|
|
{
|
|
in: "{ true } >> { false } ~ { `a` }",
|
|
expected: newSpansetOperation(OpSpansetSibling,
|
|
newSpansetOperation(OpSpansetDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false))),
|
|
newSpansetFilter(NewStaticString("a")),
|
|
),
|
|
},
|
|
{
|
|
in: "{ true } ~ { false } >> { `a` }",
|
|
expected: newSpansetOperation(OpSpansetDescendant,
|
|
newSpansetOperation(OpSpansetSibling, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false))),
|
|
newSpansetFilter(NewStaticString("a")),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetExpressionOperators(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected SpansetOperation
|
|
}{
|
|
{in: "{ true } && { false }", expected: newSpansetOperation(OpSpansetAnd, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } > { false }", expected: newSpansetOperation(OpSpansetChild, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } < { false }", expected: newSpansetOperation(OpSpansetParent, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } >> { false }", expected: newSpansetOperation(OpSpansetDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } << { false }", expected: newSpansetOperation(OpSpansetAncestor, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } || { false }", expected: newSpansetOperation(OpSpansetUnion, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } ~ { false }", expected: newSpansetOperation(OpSpansetSibling, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
// this test was added to highlight the one shift/reduce conflict in the grammar. this could also be parsed as two spanset pipelines &&ed together.
|
|
{in: "({ true }) && ({ false })", expected: newSpansetOperation(OpSpansetAnd, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } !> { false }", expected: newSpansetOperation(OpSpansetNotChild, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } !< { false }", expected: newSpansetOperation(OpSpansetNotParent, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } !>> { false }", expected: newSpansetOperation(OpSpansetNotDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } !<< { false }", expected: newSpansetOperation(OpSpansetNotAncestor, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } !~ { false }", expected: newSpansetOperation(OpSpansetNotSibling, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } &> { false }", expected: newSpansetOperation(OpSpansetUnionChild, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } &< { false }", expected: newSpansetOperation(OpSpansetUnionParent, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } &>> { false }", expected: newSpansetOperation(OpSpansetUnionDescendant, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } &<< { false }", expected: newSpansetOperation(OpSpansetUnionAncestor, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
{in: "{ true } &~ { false }", expected: newSpansetOperation(OpSpansetUnionSibling, newSpansetFilter(NewStaticBool(true)), newSpansetFilter(NewStaticBool(false)))},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScalarExpressionErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "(avg(.foo) > count()) + sum(.bar)", err: newParseError("syntax error: unexpected +, expecting with", 1, 23)},
|
|
{in: "count(", err: newParseError("syntax error: unexpected $end, expecting )", 1, 7)},
|
|
{in: "count(avg)", err: newParseError("syntax error: unexpected avg, expecting )", 1, 7)},
|
|
{in: "count(.thing)", err: newParseError("syntax error: unexpected ., expecting )", 1, 7)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScalarExpressionPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected ScalarFilter
|
|
}{
|
|
{
|
|
in: "avg(.foo) > count() + sum(.bar)",
|
|
expected: newScalarFilter(OpGreater,
|
|
newAggregate(aggregateAvg, NewAttribute("foo")),
|
|
newScalarOperation(OpAdd,
|
|
newAggregate(aggregateCount, nil),
|
|
newAggregate(aggregateSum, NewAttribute("bar")),
|
|
),
|
|
),
|
|
},
|
|
{
|
|
in: "avg(.foo) + count() > sum(.bar)",
|
|
expected: newScalarFilter(OpGreater,
|
|
newScalarOperation(OpAdd,
|
|
newAggregate(aggregateAvg, NewAttribute("foo")),
|
|
newAggregate(aggregateCount, nil),
|
|
),
|
|
newAggregate(aggregateSum, NewAttribute("bar")),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScalarExpressionOperators(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected ScalarFilter
|
|
}{
|
|
{in: "count() > 1", expected: newScalarFilter(OpGreater, newAggregate(aggregateCount, nil), NewStaticInt(1))},
|
|
{in: "max(.a) > 1", expected: newScalarFilter(OpGreater, newAggregate(aggregateMax, NewAttribute("a")), NewStaticInt(1))},
|
|
{in: "min(1) > 1", expected: newScalarFilter(OpGreater, newAggregate(aggregateMin, NewStaticInt(1)), NewStaticInt(1))},
|
|
{in: "sum(true) > 1", expected: newScalarFilter(OpGreater, newAggregate(aggregateSum, NewStaticBool(true)), NewStaticInt(1))},
|
|
{in: "avg(`c`) > 1", expected: newScalarFilter(OpGreater, newAggregate(aggregateAvg, NewStaticString("c")), NewStaticInt(1))},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(tc.expected)), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetFilterErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "wharblgarbl", err: newParseError("syntax error: unexpected IDENTIFIER", 1, 1)},
|
|
{in: "{ 2 <> 3}", err: newParseError("syntax error: unexpected >", 1, 6)},
|
|
{in: "{ 2 = .b ", err: newParseError("syntax error: unexpected $end", 1, 10)},
|
|
{in: "{ + }", err: newParseError("syntax error: unexpected +", 1, 3)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetFilterOperatorPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected FieldExpression
|
|
}{
|
|
{
|
|
in: "{ .a * .b + .c }",
|
|
expected: newBinaryOperation(OpAdd,
|
|
newBinaryOperation(OpMult, NewAttribute("a"), NewAttribute("b")),
|
|
NewAttribute("c")),
|
|
},
|
|
{
|
|
in: "{ .a + .b * .c }",
|
|
expected: newBinaryOperation(OpAdd,
|
|
NewAttribute("a"),
|
|
newBinaryOperation(OpMult, NewAttribute("b"), NewAttribute("c"))),
|
|
},
|
|
{
|
|
in: "{ ( .a + .b ) * .c }",
|
|
expected: newBinaryOperation(OpMult,
|
|
newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b")),
|
|
NewAttribute("c")),
|
|
},
|
|
{
|
|
in: "{ .a + .b ^ .c }",
|
|
expected: newBinaryOperation(OpAdd,
|
|
NewAttribute("a"),
|
|
newBinaryOperation(OpPower, NewAttribute("b"), NewAttribute("c"))),
|
|
},
|
|
{
|
|
in: "{ .a = .b + .c }",
|
|
expected: newBinaryOperation(OpEqual,
|
|
NewAttribute("a"),
|
|
newBinaryOperation(OpAdd, NewAttribute("b"), NewAttribute("c"))),
|
|
},
|
|
{
|
|
in: "{ .a + .b = .c }",
|
|
expected: newBinaryOperation(OpEqual,
|
|
newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b")),
|
|
NewAttribute("c")),
|
|
},
|
|
{
|
|
in: "{ .c - -.a + .b }",
|
|
expected: newBinaryOperation(OpAdd,
|
|
newBinaryOperation(OpSub, NewAttribute("c"), newUnaryOperation(OpSub, NewAttribute("a"))),
|
|
NewAttribute("b")),
|
|
},
|
|
{
|
|
in: "{ .c - -( .a + .b ) }",
|
|
expected: newBinaryOperation(OpSub,
|
|
NewAttribute("c"),
|
|
newUnaryOperation(OpSub, newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b")))),
|
|
},
|
|
{
|
|
in: "{ .a && .b = .c }",
|
|
expected: newBinaryOperation(OpAnd,
|
|
NewAttribute("a"),
|
|
newBinaryOperation(OpEqual, NewAttribute("b"), NewAttribute("c"))),
|
|
},
|
|
{
|
|
in: "{ .a = .b && .c }",
|
|
expected: newBinaryOperation(OpAnd,
|
|
newBinaryOperation(OpEqual, NewAttribute("a"), NewAttribute("b")),
|
|
NewAttribute("c")),
|
|
},
|
|
{
|
|
in: "{ .a = !.b && .c }",
|
|
expected: newBinaryOperation(OpAnd,
|
|
newBinaryOperation(OpEqual, NewAttribute("a"), newUnaryOperation(OpNot, NewAttribute("b"))),
|
|
NewAttribute("c")),
|
|
},
|
|
{
|
|
in: "{ .a = !( .b && .c ) }",
|
|
expected: newBinaryOperation(OpEqual,
|
|
NewAttribute("a"),
|
|
newUnaryOperation(OpNot, newBinaryOperation(OpAnd, NewAttribute("b"), NewAttribute("c")))),
|
|
},
|
|
{
|
|
in: "{ .a = .b || .c = .d}",
|
|
expected: newBinaryOperation(OpOr,
|
|
newBinaryOperation(OpEqual, NewAttribute("a"), NewAttribute("b")),
|
|
newBinaryOperation(OpEqual, NewAttribute("c"), NewAttribute("d"))),
|
|
},
|
|
{
|
|
in: "{ !.a = .b }",
|
|
expected: newBinaryOperation(OpEqual,
|
|
newUnaryOperation(OpNot, NewAttribute("a")),
|
|
NewAttribute("b")),
|
|
},
|
|
{
|
|
in: "{ !(.a = .b) }",
|
|
expected: newUnaryOperation(OpNot, newBinaryOperation(OpEqual,
|
|
NewAttribute("a"),
|
|
NewAttribute("b"))),
|
|
},
|
|
{
|
|
in: "{ -.a = .b }",
|
|
expected: newBinaryOperation(OpEqual,
|
|
newUnaryOperation(OpSub, NewAttribute("a")),
|
|
NewAttribute("b")),
|
|
},
|
|
{
|
|
in: "{ -(.a = .b) }",
|
|
expected: newUnaryOperation(OpSub, newBinaryOperation(OpEqual,
|
|
NewAttribute("a"),
|
|
NewAttribute("b"))),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(tc.expected))), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetFilterStatics(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected FieldExpression
|
|
}{
|
|
{in: "{ true }", expected: NewStaticBool(true)},
|
|
{in: "{ false }", expected: NewStaticBool(false)},
|
|
{in: `{ "true" }`, expected: NewStaticString("true")},
|
|
{in: `{ "true\"" }`, expected: NewStaticString("true\"")},
|
|
{in: "{ `foo` }", expected: NewStaticString("foo")},
|
|
{in: "{ .foo }", expected: NewAttribute("foo")},
|
|
{in: "{ duration }", expected: NewIntrinsic(IntrinsicDuration)},
|
|
{in: "{ childCount }", expected: NewIntrinsic(IntrinsicChildCount)},
|
|
{in: "{ name }", expected: NewIntrinsic(IntrinsicName)},
|
|
{in: "{ parent }", expected: NewIntrinsic(IntrinsicParent)},
|
|
{in: "{ status }", expected: NewIntrinsic(IntrinsicStatus)},
|
|
{in: "{ statusMessage }", expected: NewIntrinsic(IntrinsicStatusMessage)},
|
|
{in: "{ 4321 }", expected: NewStaticInt(4321)},
|
|
{in: "{ 1.234 }", expected: NewStaticFloat(1.234)},
|
|
{in: "{ nil }", expected: NewStaticNil()},
|
|
{in: "{ 3h }", expected: NewStaticDuration(3 * time.Hour)},
|
|
{in: "{ 1.5m }", expected: NewStaticDuration(1*time.Minute + 30*time.Second)},
|
|
{in: "{ error }", expected: NewStaticStatus(StatusError)},
|
|
{in: "{ ok }", expected: NewStaticStatus(StatusOk)},
|
|
{in: "{ unset }", expected: NewStaticStatus(StatusUnset)},
|
|
{in: "{ unspecified }", expected: NewStaticKind(KindUnspecified)},
|
|
{in: "{ internal }", expected: NewStaticKind(KindInternal)},
|
|
{in: "{ client }", expected: NewStaticKind(KindClient)},
|
|
{in: "{ server }", expected: NewStaticKind(KindServer)},
|
|
{in: "{ producer }", expected: NewStaticKind(KindProducer)},
|
|
{in: "{ consumer }", expected: NewStaticKind(KindConsumer)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(tc.expected))), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpansetFilterOperators(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected FieldExpression
|
|
alsoTestWithoutSpace bool
|
|
}{
|
|
{in: "{ .a + .b }", expected: newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b"))},
|
|
{in: "{ .a - .b }", expected: newBinaryOperation(OpSub, NewAttribute("a"), NewAttribute("b"))},
|
|
{in: "{ .a / .b }", expected: newBinaryOperation(OpDiv, NewAttribute("a"), NewAttribute("b"))},
|
|
{in: "{ .a % .b }", expected: newBinaryOperation(OpMod, NewAttribute("a"), NewAttribute("b"))},
|
|
{in: "{ .a * .b }", expected: newBinaryOperation(OpMult, NewAttribute("a"), NewAttribute("b"))},
|
|
{in: "{ .a = .b }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a != .b }", expected: newBinaryOperation(OpNotEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a =~ .b }", expected: newBinaryOperation(OpRegex, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a !~ .b }", expected: newBinaryOperation(OpNotRegex, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a > .b }", expected: newBinaryOperation(OpGreater, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a >= .b }", expected: newBinaryOperation(OpGreaterEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a < .b }", expected: newBinaryOperation(OpLess, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a <= .b }", expected: newBinaryOperation(OpLessEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a ^ .b }", expected: newBinaryOperation(OpPower, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a && .b }", expected: newBinaryOperation(OpAnd, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a || .b }", expected: newBinaryOperation(OpOr, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
|
|
{in: "{ !.b }", expected: newUnaryOperation(OpNot, NewAttribute("b"))},
|
|
{in: "{ -.b }", expected: newUnaryOperation(OpSub, NewAttribute("b"))},
|
|
|
|
// Against statics
|
|
{in: "{ .a = `foo` }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticString("foo")), alsoTestWithoutSpace: true},
|
|
{in: "{ .a = 3 }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticInt(3)), alsoTestWithoutSpace: true},
|
|
{in: "{ .a = 3.0 }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticFloat(3)), alsoTestWithoutSpace: true},
|
|
{in: "{ .a = true }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticBool(true)), alsoTestWithoutSpace: true},
|
|
|
|
// existence
|
|
{in: "{ .a != nil }", expected: newBinaryOperation(OpNotEqual, NewAttribute("a"), NewStaticNil()), alsoTestWithoutSpace: true},
|
|
{in: "{ .a = nil }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticNil()), alsoTestWithoutSpace: true},
|
|
}
|
|
|
|
test := func(q string, expected FieldExpression) {
|
|
actual, err := Parse(q)
|
|
require.NoError(t, err, q)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(expected))), actual, q)
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(_ *testing.T) {
|
|
test(tc.in, tc.expected)
|
|
if tc.alsoTestWithoutSpace {
|
|
test(strings.ReplaceAll(tc.in, " ", ""), tc.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributeNameErrors(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
err error
|
|
}{
|
|
{in: "{ . foo }", err: newParseError("syntax error: unexpected END_ATTRIBUTE, expecting IDENTIFIER", 1, 3)},
|
|
{in: `{ . "foo" }`, err: newParseError("syntax error: unexpected END_ATTRIBUTE, expecting IDENTIFIER", 1, 3)},
|
|
{in: "{ .foo .bar }", err: newParseError("syntax error: unexpected .", 1, 8)},
|
|
{in: "{ parent. }", err: newParseError("syntax error: unexpected END_ATTRIBUTE, expecting IDENTIFIER or resource. or span.", 0, 3)},
|
|
{in: ".3foo", err: newParseError("syntax error: unexpected IDENTIFIER", 1, 3)},
|
|
{in: `{ ."foo }`, err: newParseError(`unexpected EOF, expecting "`, 0, 3)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
_, err := Parse(tc.in)
|
|
|
|
require.Equal(t, tc.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBinaryAndUnaryOperationsCollapseToStatics tests code in the newBinaryOperation and newUnaryOperation functions
|
|
// that attempts to simplify combinations of static values where possible.
|
|
func TestBinaryAndUnaryOperationsCollapseToStatics(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected FieldExpression
|
|
}{
|
|
{in: "{ duration > 1 + 2}", expected: newBinaryOperation(OpGreater, NewIntrinsic(IntrinsicDuration), NewStaticInt(3))},
|
|
{in: "{ -1 }", expected: NewStaticInt(-1)},
|
|
{in: "{ 1 + 1 > 1 }", expected: NewStaticBool(true)},
|
|
{in: "{ `foo` = `bar` }", expected: NewStaticBool(false)},
|
|
{in: "{ 1 = 1. }", expected: NewStaticBool(true)}, // this is an interesting case, it returns true even though { span.foo = 1 } would be false if span.foo had the float value 1.0
|
|
{in: "{ .1 + 1 }", expected: NewStaticFloat(1.1)},
|
|
{in: "{ 1 * -1 = -1 }", expected: NewStaticBool(true)},
|
|
{in: "{ .foo * -1. = -1 }", expected: newBinaryOperation(OpEqual, newBinaryOperation(OpMult, NewAttribute("foo"), NewStaticFloat(-1)), NewStaticInt(-1))},
|
|
}
|
|
|
|
test := func(t *testing.T, q string, expected FieldExpression) {
|
|
actual, err := Parse(q)
|
|
require.NoError(t, err, q)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(expected))), actual, q)
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
test(t, tc.in, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributes(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected FieldExpression
|
|
}{
|
|
{in: "duration", expected: NewIntrinsic(IntrinsicDuration)},
|
|
{in: "kind", expected: NewIntrinsic(IntrinsicKind)},
|
|
{in: ".foo", expected: NewAttribute("foo")},
|
|
{in: ".max", expected: NewAttribute("max")},
|
|
{in: ".status", expected: NewAttribute("status")},
|
|
{in: ".kind", expected: NewAttribute("kind")},
|
|
{in: ".foo.bar", expected: NewAttribute("foo.bar")},
|
|
{in: ".foo.bar.baz", expected: NewAttribute("foo.bar.baz")},
|
|
{in: ".foo.3", expected: NewAttribute("foo.3")},
|
|
{in: ".foo3", expected: NewAttribute("foo3")},
|
|
{in: ".http_status", expected: NewAttribute("http_status")},
|
|
{in: ".http-status", expected: NewAttribute("http-status")},
|
|
{in: ".http+", expected: NewAttribute("http+")},
|
|
{in: ".😝", expected: NewAttribute("😝")},
|
|
{in: ".http-other", expected: NewAttribute("http-other")},
|
|
{in: "parent.duration", expected: NewScopedAttribute(AttributeScopeNone, true, "duration")},
|
|
{in: "parent.foo.bar.baz", expected: NewScopedAttribute(AttributeScopeNone, true, "foo.bar.baz")},
|
|
{in: "resource.foo.bar.baz", expected: NewScopedAttribute(AttributeScopeResource, false, "foo.bar.baz")},
|
|
{in: "span.foo.bar", expected: NewScopedAttribute(AttributeScopeSpan, false, "foo.bar")},
|
|
{in: "event.foo.bar", expected: NewScopedAttribute(AttributeScopeEvent, false, "foo.bar")},
|
|
{in: "link.foo.bar", expected: NewScopedAttribute(AttributeScopeLink, false, "foo.bar")},
|
|
{in: "instrumentation.foo.bar", expected: NewScopedAttribute(AttributeScopeInstrumentation, false, "foo.bar")},
|
|
{in: "parent.resource.foo", expected: NewScopedAttribute(AttributeScopeResource, true, "foo")},
|
|
{in: "parent.span.foo", expected: NewScopedAttribute(AttributeScopeSpan, true, "foo")},
|
|
{in: "parent.resource.foo.bar.baz", expected: NewScopedAttribute(AttributeScopeResource, true, "foo.bar.baz")},
|
|
{in: "parent.span.foo.bar", expected: NewScopedAttribute(AttributeScopeSpan, true, "foo.bar")},
|
|
{in: `."bar z".foo`, expected: NewAttribute("bar z.foo")},
|
|
{in: `span."bar z".foo`, expected: NewScopedAttribute(AttributeScopeSpan, false, "bar z.foo")},
|
|
{in: `."bar z".foo."bar"`, expected: NewAttribute("bar z.foo.bar")},
|
|
{in: `.foo."bar baz"`, expected: NewAttribute("foo.bar baz")},
|
|
{in: `.foo."bar baz".bar`, expected: NewAttribute("foo.bar baz.bar")},
|
|
{in: `.foo."bar \" baz"`, expected: NewAttribute(`foo.bar " baz`)},
|
|
{in: `.foo."bar \\ baz"`, expected: NewAttribute(`foo.bar \ baz`)},
|
|
{in: `.foo."bar \\"." baz"`, expected: NewAttribute(`foo.bar \. baz`)},
|
|
{in: `."foo.bar"`, expected: NewAttribute(`foo.bar`)},
|
|
{in: `."🤘"`, expected: NewAttribute(`🤘`)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
s := "{ " + tc.in + " }"
|
|
actual, err := Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(tc.expected))), actual)
|
|
|
|
s = "{" + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(tc.expected))), actual)
|
|
|
|
s = "{ (" + tc.in + ") }"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(tc.expected))), actual)
|
|
|
|
s = "{ " + tc.in + " + " + tc.in + " }"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(newBinaryOperation(OpAdd, tc.expected, tc.expected)))), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntrinsics(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected Intrinsic
|
|
}{
|
|
{in: "duration", expected: IntrinsicDuration},
|
|
{in: "childCount", expected: IntrinsicChildCount},
|
|
{in: "name", expected: IntrinsicName},
|
|
{in: "status", expected: IntrinsicStatus},
|
|
{in: "statusMessage", expected: IntrinsicStatusMessage},
|
|
{in: "kind", expected: IntrinsicKind},
|
|
{in: "parent", expected: IntrinsicParent},
|
|
{in: "traceDuration", expected: IntrinsicTraceDuration},
|
|
{in: "rootServiceName", expected: IntrinsicTraceRootService},
|
|
{in: "rootName", expected: IntrinsicTraceRootSpan},
|
|
{in: "nestedSetLeft", expected: IntrinsicNestedSetLeft},
|
|
{in: "nestedSetRight", expected: IntrinsicNestedSetRight},
|
|
{in: "nestedSetParent", expected: IntrinsicNestedSetParent},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
// as intrinsic e.g. duration
|
|
s := "{ " + tc.in + " }"
|
|
actual, err := Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeNone,
|
|
Parent: false,
|
|
Name: tc.in,
|
|
Intrinsic: tc.expected,
|
|
}))), actual)
|
|
|
|
// as attribute e.g .duration
|
|
s = "{ ." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeNone,
|
|
Parent: false,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as span scoped attribute e.g span.duration
|
|
s = "{ span." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeSpan,
|
|
Parent: false,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as resource scoped attribute e.g resource.duration
|
|
s = "{ resource." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeResource,
|
|
Parent: false,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as parent scoped intrinsic e.g parent.duration
|
|
s = "{ parent." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeNone,
|
|
Parent: true,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as nested parent scoped intrinsic e.g. parent.duration.foo
|
|
// this becomes lookup on attribute named "duration.foo"
|
|
s = "{ parent." + tc.in + ".foo }"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeNone,
|
|
Parent: true,
|
|
Name: tc.in + ".foo",
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as parent resource scoped attribute e.g. parent.resource.duration
|
|
s = "{ parent.resource." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeResource,
|
|
Parent: true,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
|
|
// as parent span scoped attribute e.g. praent.span.duration
|
|
s = "{ parent.span." + tc.in + "}"
|
|
actual, err = Parse(s)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeSpan,
|
|
Parent: true,
|
|
Name: tc.in,
|
|
Intrinsic: IntrinsicNone,
|
|
}))), actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopedIntrinsics(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected Intrinsic
|
|
shouldError bool
|
|
}{
|
|
{in: "trace:duration", expected: IntrinsicTraceDuration},
|
|
{in: "trace:rootName", expected: IntrinsicTraceRootSpan},
|
|
{in: "trace:rootService", expected: IntrinsicTraceRootService},
|
|
{in: "trace:id", expected: IntrinsicTraceID},
|
|
{in: "span:duration", expected: IntrinsicDuration},
|
|
{in: "span:kind", expected: IntrinsicKind},
|
|
{in: "span:name", expected: IntrinsicName},
|
|
{in: "span:status", expected: IntrinsicStatus},
|
|
{in: "span:statusMessage", expected: IntrinsicStatusMessage},
|
|
{in: "span:id", expected: IntrinsicSpanID},
|
|
{in: "span:parentID", expected: IntrinsicParentID},
|
|
{in: "event:name", expected: IntrinsicEventName},
|
|
{in: "event:timeSinceStart", expected: IntrinsicEventTimeSinceStart},
|
|
{in: "link:traceID", expected: IntrinsicLinkTraceID},
|
|
{in: "link:spanID", expected: IntrinsicLinkSpanID},
|
|
{in: "instrumentation:name", expected: IntrinsicInstrumentationName},
|
|
{in: "instrumentation:version", expected: IntrinsicInstrumentationVersion},
|
|
{in: ":duration", shouldError: true},
|
|
{in: ":statusMessage", shouldError: true},
|
|
{in: "trace:name", shouldError: true},
|
|
{in: "trace:rootServiceName", shouldError: true},
|
|
{in: "span:rootServiceName", shouldError: true},
|
|
{in: "parent:id", shouldError: true},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
// as scoped intrinsic e.g :duration
|
|
s := "{ " + tc.in + "}"
|
|
actual, err := Parse(s)
|
|
|
|
if tc.shouldError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, newRootExpr(newPipeline(
|
|
newSpansetFilter(Attribute{
|
|
Scope: AttributeScopeNone,
|
|
Parent: false,
|
|
Name: tc.expected.String(),
|
|
Intrinsic: tc.expected,
|
|
}))), actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseIdentifier(t *testing.T) {
|
|
testCases := map[string]Attribute{
|
|
"name": NewIntrinsic(IntrinsicName),
|
|
"status": NewIntrinsic(IntrinsicStatus),
|
|
"statusMessage": NewIntrinsic(IntrinsicStatusMessage),
|
|
"kind": NewIntrinsic(IntrinsicKind),
|
|
".name": NewAttribute("name"),
|
|
".status": NewAttribute("status"),
|
|
".foo.bar": NewAttribute("foo.bar"),
|
|
"resource.foo.bar": NewScopedAttribute(AttributeScopeResource, false, "foo.bar"),
|
|
"span.foo.bar": NewScopedAttribute(AttributeScopeSpan, false, "foo.bar"),
|
|
}
|
|
for i, expected := range testCases {
|
|
actual, err := ParseIdentifier(i)
|
|
require.NoError(t, err, i)
|
|
require.Equal(t, expected, actual, i)
|
|
}
|
|
}
|
|
|
|
func TestEmptyQuery(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
}{
|
|
{in: "{}"},
|
|
{in: "{ }"},
|
|
{in: "{ }"},
|
|
{in: "{ true }"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
require.NoError(t, err, tc.in)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(NewStaticBool(true)))), actual, tc.in)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHints(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected *RootExpr
|
|
}{
|
|
{
|
|
in: `{ } | rate() with(foo="bar")`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregate(metricsAggregateRate, nil),
|
|
).withHints(newHints([]*Hint{
|
|
newHint("foo", NewStaticString("bar")),
|
|
})),
|
|
},
|
|
{
|
|
in: `{ } | rate() with(foo=0.5)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregate(metricsAggregateRate, nil),
|
|
).withHints(newHints([]*Hint{
|
|
newHint("foo", NewStaticFloat(0.5)),
|
|
})),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReallyLongQuery(t *testing.T) {
|
|
for i := 1000; i < 1050; i++ {
|
|
longVal := strings.Repeat("a", i)
|
|
|
|
// static value
|
|
query := fmt.Sprintf("{ .a = `%s` }", longVal)
|
|
expected := newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticString(longVal))
|
|
|
|
actual, err := Parse(query)
|
|
|
|
require.NoError(t, err, "i=%d", i)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(expected))), actual, "i=%d", i)
|
|
|
|
// attr name
|
|
query = fmt.Sprintf("{ .%s = `foo` }", longVal)
|
|
expected = newBinaryOperation(OpEqual, NewAttribute(longVal), NewStaticString("foo"))
|
|
|
|
actual, err = Parse(query)
|
|
|
|
require.NoError(t, err, "i=%d", i)
|
|
require.Equal(t, newRootExpr(newPipeline(newSpansetFilter(expected))), actual, "i=%d", i)
|
|
}
|
|
}
|
|
|
|
func TestMetrics(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
expected *RootExpr
|
|
}{
|
|
{
|
|
in: `{ } | rate()`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregate(metricsAggregateRate, nil),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | count_over_time() by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregate(metricsAggregateCountOverTime, []Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | min_over_time(duration) by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregateWithAttr(metricsAggregateMinOverTime,
|
|
NewIntrinsic(IntrinsicDuration),
|
|
[]Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | max_over_time(duration) by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregateWithAttr(metricsAggregateMaxOverTime,
|
|
NewIntrinsic(IntrinsicDuration),
|
|
[]Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | avg_over_time(duration) by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newAverageOverTimeMetricsAggregator(
|
|
NewIntrinsic(IntrinsicDuration),
|
|
[]Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | sum_over_time(duration) by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregateWithAttr(metricsAggregateSumOverTime,
|
|
NewIntrinsic(IntrinsicDuration),
|
|
[]Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
in: `{ } | quantile_over_time(duration, 0, 0.90, 0.95, 1) by(name, span.http.status_code)`,
|
|
expected: newRootExprWithMetrics(
|
|
newPipeline(newSpansetFilter(NewStaticBool(true))),
|
|
newMetricsAggregateQuantileOverTime(
|
|
NewIntrinsic(IntrinsicDuration),
|
|
[]float64{0, 0.9, 0.95, 1.0},
|
|
[]Attribute{
|
|
NewIntrinsic(IntrinsicName),
|
|
NewScopedAttribute(AttributeScopeSpan, false, "http.status_code"),
|
|
}),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
actual, err := Parse(tc.in)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, actual)
|
|
})
|
|
}
|
|
}
|