mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* Add operators IN, NOT IN, MATCH ANY, and MATCH NONE
* Implement Static.append()
* Print string arrays with '`' instead of '"'
Example ["aa", "bb"] -> [`aa`, `bb`]
* Rewrite binary operations to array equivalent
The rewriter handles the following cases:
- { .a = "a" || .a = "b" } => { .a IN ["a", "b"] }
- { .a != "a" && .a != "b" } => { .a NOT IN ["a", "b"] }
- { .a =~ "a" || .a =~ "b" } => { .a MATCH ANY ["a", "b"] }
- { .a !~ "a" && .a !~ "b" } => { .a MATCH NONE ["a", "b"] }
* Add test cases with arrays to TestBinOp
* traceql.Parse() applies AST optimizations by default
* Rename predicate_test.go to predicates_test.go
* Add array predicates to parquetquery
* BREAKING CHANGE: more precise regex validation
BinaryOperation.validate() considered everythin a valid regex where
FieldExpression.EncodeToString(false) happened to return a valid regex.
With this change the RHS of a regex operation is required to be a string
or string array
* vp4: handle predicates with arrays correctly
* vp5: handle predicates with arrays correctly
* vp3: handle predicates with arrays correctly
* Add test cases for optimized queries
* Update query test examples with optimizable queries
* vp3: fix handling of array types
* CHANGELOG.md
* Pass array operators down to fetch layer
* Read to first virtual row after seeking on a low definition level
This helps avoid additional spans/op in queries with only
resource attributes
---------
Co-authored-by: Martin Disibio <[email protected]>
2226 lines
81 KiB
Go
2226 lines
81 KiB
Go
package traceql
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type evalTC struct {
|
|
query string
|
|
input []*Spanset
|
|
output []*Spanset
|
|
}
|
|
|
|
const testIterations = 5
|
|
|
|
func testEvaluator(t *testing.T, tc evalTC) {
|
|
t.Helper()
|
|
|
|
t.Run(tc.query, func(t *testing.T) {
|
|
ast, err := Parse(tc.query)
|
|
require.NoError(t, err)
|
|
|
|
// run tests multiple times to ensure optimizations like regex caching are working
|
|
for range testIterations {
|
|
// clone input to confirm it doesn't get modified
|
|
cloneIn := make([]*Spanset, len(tc.input))
|
|
for i := range tc.input {
|
|
cloneIn[i] = tc.input[i].clone()
|
|
cloneIn[i].Spans = append([]Span(nil), tc.input[i].Spans...)
|
|
}
|
|
|
|
actual, err := ast.Pipeline.evaluate(tc.input)
|
|
require.NoError(t, err)
|
|
|
|
// sort expected/actual spansets. grouping requires this b/c map iteration makes the output
|
|
// non-deterministic.
|
|
makeSort := func(ss []*Spanset) func(i, j int) bool {
|
|
return func(i, j int) bool {
|
|
return bytes.Compare(ss[i].Spans[0].ID(), ss[j].Spans[0].ID()) < 0
|
|
}
|
|
}
|
|
sort.Slice(actual, makeSort(actual))
|
|
sort.Slice(tc.output, makeSort(tc.output))
|
|
|
|
require.Equal(t, tc.output, actual)
|
|
require.Equal(t, tc.input, cloneIn)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSpansetFilter_matches(t *testing.T) {
|
|
tests := []struct {
|
|
query string
|
|
span Span
|
|
matches bool
|
|
}{
|
|
{
|
|
query: `{ ("foo" != "bar") && !("foo" = "bar") }`,
|
|
span: &mockSpan{
|
|
attributes: nil,
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .foo = .bar }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("bzz"),
|
|
NewAttribute("bar"): NewStaticString("bzz"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
// Missing attribute
|
|
query: `{ .foo = "bar" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("fzz"): NewStaticString("bar"),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
{
|
|
query: `{ .foo = .bar }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("str"),
|
|
NewAttribute("bar"): NewStaticInt(5),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
{
|
|
// Types don't match with operator
|
|
query: `{ .foo =~ .bar }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticInt(3),
|
|
NewAttribute("bar"): NewStaticInt(5),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
{
|
|
query: `{ .field1 =~ "hello w.*" && .field2 !~ "bye b.*" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("field1"): NewStaticString("hello world"),
|
|
NewAttribute("field2"): NewStaticString("bye world"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .foo > 2 && .foo >= 3.5 && .foo < 5 && .foo <= 3.5 && .duration > 1800ms }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticFloat(3.5),
|
|
NewAttribute("duration"): NewStaticDuration(2 * time.Second),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .foo = "scope_span" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewScopedAttribute(AttributeScopeSpan, false, "foo"): NewStaticString("scope_span"),
|
|
NewScopedAttribute(AttributeScopeResource, false, "foo"): NewStaticString("scope_resource"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .foo = "scope_resource" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewScopedAttribute(AttributeScopeResource, false, "foo"): NewStaticString("scope_resource"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ span.foo = "scope_span" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewScopedAttribute(AttributeScopeSpan, false, "foo"): NewStaticString("scope_span"),
|
|
NewScopedAttribute(AttributeScopeResource, false, "foo"): NewStaticString("scope_resource"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ resource.foo = "scope_resource" }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewScopedAttribute(AttributeScopeSpan, false, "foo"): NewStaticString("scope_span"),
|
|
NewScopedAttribute(AttributeScopeResource, false, "foo"): NewStaticString("scope_resource"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
// create a evalTC and use testEvaluator
|
|
tc := evalTC{
|
|
query: tt.query,
|
|
input: []*Spanset{
|
|
{Spans: []Span{tt.span}},
|
|
},
|
|
output: []*Spanset{},
|
|
}
|
|
if tt.matches {
|
|
tc.output = tc.input
|
|
}
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestGroup(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ } | by(.foo)",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("a")}},
|
|
},
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("b")}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"{ } | by(.foo) | by(.bar)",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a"), NewAttribute("bar"): NewStaticString("1")}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b"), NewAttribute("bar"): NewStaticString("1")}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b"), NewAttribute("bar"): NewStaticString("2")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a"), NewAttribute("bar"): NewStaticString("1")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("a")}, {Name: "by(.bar)", Val: NewStaticString("1")}},
|
|
},
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b"), NewAttribute("bar"): NewStaticString("1")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("b")}, {Name: "by(.bar)", Val: NewStaticString("1")}},
|
|
},
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b"), NewAttribute("bar"): NewStaticString("2")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("b")}, {Name: "by(.bar)", Val: NewStaticString("2")}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestCoalesce(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ } | coalesce()",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
{
|
|
Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
},
|
|
// coalesce() should drop attributes
|
|
Attributes: []*SpansetAttribute{{Name: "by(.foo)", Val: NewStaticString("a")}},
|
|
},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestSpansetOperationEvaluate(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ .foo = `a` } && { .foo = `b` }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// This spanset will be kept because it satisfies both conditions
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
{Spans: []Span{
|
|
// This spanset will be dropped
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = `a` } || { .foo = `b` }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
{Spans: []Span{
|
|
// Second span will be dropped
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{4}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("c")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ true } && { true } && { true }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ true } || { true } || { true }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .parent } >> { .child }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .child } << { .parent }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .parent } > { .child }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .child } < { .parent }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .child1 } ~ { .child2 }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child1", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(1, 4, 5),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(1, 4, 5),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ } !< { .child }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ } !> { .parent }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .child1 } !~ { .child2 }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child1", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(1, 4, 5),
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(4, 5, 6),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(4, 5, 6),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ } !<< { .child }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ } !>> { .parent }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{2}).WithAttrBool("child", true).WithNestedSetInfo(1, 2, 3),
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent", true).WithNestedSetInfo(0, 1, 4),
|
|
}},
|
|
},
|
|
},
|
|
{ // tests that child operators do not modify the spanset
|
|
"{ } > { } > { } > { }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("child1", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{1}).WithAttrBool("child2", true).WithNestedSetInfo(1, 4, 5),
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
{ // tests that parent operators do not modify the spanset
|
|
"{ } < { } < { } < { }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent1", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{1}).WithAttrBool("parent2", true).WithNestedSetInfo(1, 4, 5),
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
{ // tests that parent operators do not modify the spanset
|
|
"{ } &< { } &< { } &< { }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
newMockSpan([]byte{1}).WithAttrBool("parent1", true).WithNestedSetInfo(1, 2, 3),
|
|
newMockSpan([]byte{1}).WithAttrBool("parent2", true).WithNestedSetInfo(1, 4, 5),
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
// test cases for array
|
|
func TestSpansetOperationEvaluateArray(t *testing.T) {
|
|
testCases := []evalTC{
|
|
// string arrays
|
|
{
|
|
"{ .foo = `bar` }", // match string array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = `bar` || .bat = `baz` }", // match string array with or
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo != `baz` }", // match string not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "bla"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"baz"})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "bla"})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo =~ `ba.*` }", // match string array with regex
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"foo", "baz"})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"dog", "cat"})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"foo", "baz"})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo !~ `ba.*` }", // regex not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"foo", "boo"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar"})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"foo", "boo"})}},
|
|
}},
|
|
},
|
|
},
|
|
// int arrays
|
|
{
|
|
"{ .foo = 2 }", // match int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo != 3 }", // match int not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 3})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = 2.5 }", // match float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 2.5})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 2.5})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = 3.14 }", // match another float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.14, 6.28})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.23, 4.56})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.14, 6.28})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo > 1 }", // match int array greater than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, 1})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo >= 1 }", // match int array greater equal than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, 1})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, -1})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, 1})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo < 2 }", // match int array less than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo <= 2 }", // match int array less than equal
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{2, 4})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{2, 4})}},
|
|
}},
|
|
},
|
|
},
|
|
// match float arrays
|
|
{
|
|
"{ .foo != 2.5 }", // match not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.5, 4.0})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.5})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo < 2.0 }", // match float array less than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo <= 2.0 }", // match float array less than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.0, 4.0})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo > 2.5 }", // match float array greater than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.0, 2.0})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo >= 2.5 }", // match float array greater than
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.0, 2.0})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.0, 2.5})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.0})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.0, 2.5})}},
|
|
}},
|
|
},
|
|
},
|
|
// match bool arrays
|
|
{
|
|
"{ .foo = true }", // match boolean array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = false }", // match another boolean array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo != true }", // match boolean not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = !true }", // match boolean array not equal to true
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = true && .foo = false }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
},
|
|
// TODO(adrian) enable test once BinaryOperation.execute() can handle operations where LHS and RHS are arrays
|
|
//{
|
|
// "{ .foo = true || .foo = false }",
|
|
// []*Spanset{
|
|
// {Spans: []Span{
|
|
// &mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
// &mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
// &mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
// }},
|
|
// },
|
|
// []*Spanset{
|
|
// {Spans: []Span{
|
|
// &mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
// &mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
// &mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
// }},
|
|
// },
|
|
// },
|
|
// empty arrays
|
|
{
|
|
"{ .foo = 1 }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{})}},
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
{
|
|
"{ .foo = `test` }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{})}},
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
// tests to make sure symmetric operations are supported for arrays...
|
|
func TestSpansetOperationEvaluateArraySymmetric(t *testing.T) {
|
|
testCases := []evalTC{
|
|
// string arrays
|
|
{
|
|
"{ `bar` = .foo }", // Symmetric match for string array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "baz"})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ `baz` != .foo }", // Symmetric string not in array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "bat"})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"baz"})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticStringArray([]string{"bar", "bat"})}},
|
|
}},
|
|
},
|
|
},
|
|
// int arrays
|
|
{
|
|
"{ 2 = .foo }", // Symmetric match for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3 != .foo }", // Symmetric not equal for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 2 < .foo }", // Symmetric less-than for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, 1})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// spans with any array elements grater then 2 will not be filtered out.
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 2 <= .foo }", // Symmetric less-than equal for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{0, 1})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 2})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3 > .foo }", // Symmetric grater-than for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3 >= .foo }", // Symmetric grater-than-equal for int array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
&mockSpan{id: []byte{4}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{4, 6})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 3})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{3, 4})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
},
|
|
// match float arrays
|
|
{
|
|
"{ 2.5 = .foo }", // Symmetric match for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 2.5})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.5})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 2.5})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3.14 != .foo }", // Symmetric not equal for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.23, 4.56})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.14, 6.28})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.14})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.23, 4.56})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 2.0 > .foo }", // Symmetric grater-than for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 2.0 >= .foo }", // Symmetric grater-than-equal for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{3.5, 3.0})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 4.0})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{1.5, 3.0})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3.5 < .foo }", // Symmetric less-than for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.5})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 3.5})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 5.5})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 3.5})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 5.5})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ 3.5 <= .foo }", // Symmetric less-than-equal for float array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.5})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 3.5})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 5.5})}},
|
|
&mockSpan{id: []byte{4}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 1.5})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{2.0, 3.5})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 3.5})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloatArray([]float64{4.0, 5.5})}},
|
|
}},
|
|
},
|
|
},
|
|
// match bool arrays
|
|
{
|
|
"{ true = .foo }", // Symmetric match for boolean array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ false != .foo }", // Symmetric not equal for boolean array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ !false = .foo }", // Symmetric not equal for boolean array
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
"{ true = .foo && false = .foo }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
}},
|
|
},
|
|
},
|
|
// TODO(adrian) enable test once BinaryOperation.execute() can handle operations where LHS and RHS are arrays
|
|
//{
|
|
// "{ true = .foo || false = .foo }",
|
|
// []*Spanset{
|
|
// {Spans: []Span{
|
|
// &mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
// &mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
// &mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
// }},
|
|
// },
|
|
// []*Spanset{
|
|
// {Spans: []Span{
|
|
// &mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, false})}},
|
|
// &mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{true, true})}},
|
|
// &mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticBooleanArray([]bool{false, false})}},
|
|
// }},
|
|
// },
|
|
// },
|
|
}
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestSpansetOperationEvaluateArrayUnsupported(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ .foo + 3 = 4 }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
{
|
|
"{ 4 = .foo + 3 }",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticIntArray([]int{1, 2})}},
|
|
}},
|
|
},
|
|
[]*Spanset{},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Helper()
|
|
|
|
t.Run(tc.query, func(t *testing.T) {
|
|
ast, err := Parse(tc.query)
|
|
require.NoError(t, err)
|
|
|
|
// clone input to confirm it doesn't get modified
|
|
cloneIn := make([]*Spanset, len(tc.input))
|
|
for i := range tc.input {
|
|
cloneIn[i] = tc.input[i].clone()
|
|
cloneIn[i].Spans = append([]Span(nil), tc.input[i].Spans...)
|
|
}
|
|
|
|
_, err = ast.Pipeline.evaluate(tc.input)
|
|
require.Error(t, err, errors.ErrUnsupported)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScalarFilterEvaluate(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ .foo = `a` } | count() > 1",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// This has 1 match
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("b")}},
|
|
}},
|
|
{Spans: []Span{
|
|
// This has 2 matches
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Scalar: NewStaticInt(2),
|
|
Spans: []Span{
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
&mockSpan{attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticString("a")}},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "count()", Val: NewStaticInt(2)}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"{ .foo = `a` } | avg(duration) >= 10ms",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// Avg duration = 5ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
{Spans: []Span{
|
|
// Avg duration = 10ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(5 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Scalar: NewStaticDuration(10.0 * time.Millisecond),
|
|
Spans: []Span{
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(5 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "avg(duration)", Val: NewStaticDuration(10 * time.Millisecond)}},
|
|
},
|
|
},
|
|
},
|
|
// max
|
|
{
|
|
"{ .foo = `a` } | max(duration) >= 10ms",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// max duration = 8ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
{Spans: []Span{
|
|
// max duration = 15ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(5 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Scalar: NewStaticDuration(15 * time.Millisecond),
|
|
Spans: []Span{
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(5 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "max(duration)", Val: NewStaticDuration(15 * time.Millisecond)}},
|
|
},
|
|
},
|
|
},
|
|
// min
|
|
{
|
|
"{ .foo = `a` } | min(duration) <= 10ms",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// min duration = 2ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
{Spans: []Span{
|
|
// min duration = 5ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(12 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Scalar: NewStaticDuration(2 * time.Millisecond),
|
|
Spans: []Span{
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "min(duration)", Val: NewStaticDuration(2 * time.Millisecond)}},
|
|
},
|
|
},
|
|
},
|
|
// sum
|
|
{
|
|
"{ .foo = `a` } | sum(duration) = 10ms",
|
|
[]*Spanset{
|
|
{Spans: []Span{
|
|
// sum duration = 10ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
{Spans: []Span{
|
|
// sum duration = 27ms
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(12 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(15 * time.Millisecond),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
[]*Spanset{
|
|
{
|
|
Scalar: NewStaticDuration(10 * time.Millisecond),
|
|
Spans: []Span{
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(2 * time.Millisecond),
|
|
},
|
|
},
|
|
&mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("foo"): NewStaticString("a"),
|
|
NewIntrinsic(IntrinsicDuration): NewStaticDuration(8 * time.Millisecond),
|
|
},
|
|
},
|
|
},
|
|
Attributes: []*SpansetAttribute{{Name: "sum(duration)", Val: NewStaticDuration(10 * time.Millisecond)}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestBinaryOperationsWorkAcrossNumberTypes(t *testing.T) {
|
|
testCases := []evalTC{
|
|
{
|
|
"{ .foo > 0 }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo < 2 }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo = 1 }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo > 0. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo < 2. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo = 1. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo > 0ns }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo < 2ns }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo = 1ns }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{3}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticDuration(1)}},
|
|
}}},
|
|
},
|
|
// binops work with statics
|
|
{
|
|
"{ 1 > 0. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ 0 < 2. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ 1 = 1. }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ 1ms = 1ms }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ 1ns = 1 }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
// binops work with attributes
|
|
{
|
|
"{ .foo < .bar }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(2)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(2), NewAttribute("bar"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(2)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .bar > .foo }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(2)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(2), NewAttribute("bar"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(2)}},
|
|
}}},
|
|
},
|
|
{
|
|
"{ .foo = .bar }",
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}},
|
|
&mockSpan{id: []byte{2}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(2), NewAttribute("bar"): NewStaticFloat(1)}},
|
|
}}},
|
|
[]*Spanset{{Spans: []Span{
|
|
&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}},
|
|
}}},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestBinOp(t *testing.T) {
|
|
testCases := []struct {
|
|
op Operator
|
|
lhs Static
|
|
rhs Static
|
|
expected Static
|
|
symmetric bool
|
|
err error
|
|
}{
|
|
{
|
|
op: OpGreater,
|
|
lhs: NewStaticString("foo"),
|
|
rhs: NewStaticString(""),
|
|
expected: StaticTrue,
|
|
},
|
|
// Comparisons of strings starting with a number were previously broken.
|
|
{
|
|
op: OpGreater,
|
|
lhs: NewStaticString("123"),
|
|
rhs: NewStaticString(""),
|
|
expected: StaticTrue,
|
|
},
|
|
// Test cases for of IN/NOT IN array operations
|
|
{
|
|
op: OpIn,
|
|
lhs: NewStaticInt(2),
|
|
rhs: NewStaticIntArray([]int{1, 2, 3}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpIn,
|
|
lhs: NewStaticInt(2),
|
|
rhs: NewStaticStringArray([]string{"1", "2", "3"}),
|
|
expected: StaticFalse,
|
|
},
|
|
{
|
|
op: OpNotIn,
|
|
lhs: NewStaticString("1"),
|
|
rhs: NewStaticStringArray([]string{"2", "3", "4"}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpNotIn,
|
|
lhs: NewStaticString("3"),
|
|
rhs: NewStaticStringArray([]string{"1", "2", "3"}),
|
|
expected: StaticFalse,
|
|
},
|
|
{
|
|
op: OpEqual,
|
|
lhs: NewStaticInt(2),
|
|
rhs: NewStaticIntArray([]int{1, 2, 3}),
|
|
expected: StaticTrue,
|
|
symmetric: true,
|
|
},
|
|
{
|
|
op: OpNotEqual,
|
|
lhs: NewStaticString("1"),
|
|
rhs: NewStaticStringArray([]string{"2", "3", "4"}),
|
|
expected: StaticTrue,
|
|
symmetric: true,
|
|
},
|
|
// Test cases for of array regex MATCH ANY / MATCH NONE operations
|
|
{
|
|
op: OpRegexMatchAny,
|
|
lhs: NewStaticString("two"),
|
|
rhs: NewStaticStringArray([]string{"one", "tw.*", "three"}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpRegexMatchAny,
|
|
lhs: NewStaticString("two"),
|
|
rhs: NewStaticStringArray([]string{"on.*", "three", ".*four.*"}),
|
|
expected: StaticFalse,
|
|
},
|
|
{
|
|
op: OpRegexMatchAny,
|
|
lhs: NewStaticString("1.1"),
|
|
rhs: NewStaticFloatArray([]float64{1.1, 2.2, 3.3}),
|
|
expected: StaticFalse,
|
|
},
|
|
{
|
|
op: OpRegexMatchNone,
|
|
lhs: NewStaticString("one"),
|
|
rhs: NewStaticStringArray([]string{"tw.*", "th.*", "fo.*"}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpRegexMatchNone,
|
|
lhs: NewStaticString("one"),
|
|
rhs: NewStaticStringArray([]string{"on.*", "tw.*", "th.*"}),
|
|
expected: StaticFalse,
|
|
},
|
|
{
|
|
op: OpRegex,
|
|
lhs: NewStaticString("two"),
|
|
rhs: NewStaticStringArray([]string{"one", "tw.*", "three"}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpRegex,
|
|
lhs: NewStaticStringArray([]string{"one", "two", "three"}),
|
|
rhs: NewStaticString("tw.*"),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpNotRegex,
|
|
lhs: NewStaticString("one"),
|
|
rhs: NewStaticStringArray([]string{"tw.*", "th.*", "fo.*"}),
|
|
expected: StaticTrue,
|
|
},
|
|
{
|
|
op: OpNotRegex,
|
|
lhs: NewStaticStringArray([]string{"two", "three", "four"}),
|
|
rhs: NewStaticString("on.*"),
|
|
expected: StaticTrue,
|
|
},
|
|
// Array operations expecte the RHS to be the array
|
|
{
|
|
op: OpIn,
|
|
lhs: NewStaticIntArray([]int{1, 2, 3}),
|
|
rhs: NewStaticInt(2),
|
|
err: errors.New("array operators require array on the right side"),
|
|
},
|
|
// Array operations can't have two arrays
|
|
{
|
|
op: OpNotIn,
|
|
lhs: NewStaticIntArray([]int{1, 2}),
|
|
rhs: NewStaticIntArray([]int{2, 3}),
|
|
err: errors.New("array operators must consist of a scalar and an array operand"),
|
|
},
|
|
{
|
|
op: OpEqual,
|
|
lhs: NewStaticIntArray([]int{1, 2}),
|
|
rhs: NewStaticIntArray([]int{2, 3}),
|
|
err: errors.New("array operators must consist of a scalar and an array operand"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
op := newBinaryOperation(tc.op, tc.lhs, tc.rhs)
|
|
t.Run(fmt.Sprintf("%s %s %s", tc.lhs, tc.op, tc.rhs), func(t *testing.T) {
|
|
actual, err := op.execute(nil)
|
|
if tc.err != nil {
|
|
require.EqualError(t, err, tc.err.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, actual, fmt.Sprintf("%s %s %s", tc.lhs, tc.op, tc.rhs))
|
|
}
|
|
|
|
if tc.symmetric {
|
|
op = newBinaryOperation(tc.op, tc.rhs, tc.lhs)
|
|
actual, err = op.execute(nil)
|
|
if tc.err != nil {
|
|
require.EqualError(t, err, tc.err.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, actual, fmt.Sprintf("%s %s %s", tc.rhs, tc.op, tc.lhs))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArithmetic(t *testing.T) {
|
|
testCases := []evalTC{
|
|
// static arithmetic works
|
|
{
|
|
"{ 1 + 1 = 2 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 2 - 2 > -1 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 1 / 10. = .1 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 1 / 10 = 0 }", // integer division
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 3 * 2 = 6 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 10 % 3 = 1 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 2 ^ 2 = 4 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 2m + 2m = 4m }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ 2m * 2 = 4m }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
// attribute arithmetic works
|
|
{
|
|
"{ .foo + .bar = 2 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo - 2 = -1 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo / .bar != 3 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo * .bar = 1 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo % .bar = 0 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo ^ .bar < 3 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
{
|
|
"{ .foo * 3ms = 3ms }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(1), NewAttribute("bar"): NewStaticFloat(1)}}}}},
|
|
},
|
|
// complex true
|
|
{
|
|
"{ (2 - .bar) * .foo = -15}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
},
|
|
{
|
|
"{ 2 - .bar * .foo = -19}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
},
|
|
{
|
|
"{ 2 ^ (.bar * .foo) = 2097152}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
},
|
|
{
|
|
"{ .bar % 2 = .foo - 2 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
},
|
|
// complex false
|
|
{
|
|
"{ (2 - .bar) * .foo < -15}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{},
|
|
},
|
|
{
|
|
"{ 2 - .bar * .foo > -19}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{},
|
|
},
|
|
{
|
|
"{ 2 ^ (.bar * .foo) != 2097152}",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{},
|
|
},
|
|
{
|
|
"{ .bar % 2 < .foo - 2 }",
|
|
[]*Spanset{{Spans: []Span{&mockSpan{id: []byte{1}, attributes: map[Attribute]Static{NewAttribute("foo"): NewStaticInt(3), NewAttribute("bar"): NewStaticFloat(7)}}}}},
|
|
[]*Spanset{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func TestSpansetExistence(t *testing.T) {
|
|
tests := []struct {
|
|
query string
|
|
span Span
|
|
matches bool
|
|
}{
|
|
// return traces where .foo exists
|
|
{
|
|
query: `{ .foo != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("bar"): NewStaticString("bzz"),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
{
|
|
query: `{ .bar != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("bar"): NewStaticString("bzz"),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .int != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("int"): NewStaticInt(1),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .duration != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("duration"): NewStaticDuration(time.Minute),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ .float != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("float"): NewStaticFloat(2.0),
|
|
},
|
|
},
|
|
matches: true,
|
|
},
|
|
{
|
|
query: `{ nil = nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("float"): NewStaticFloat(2.0),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
{
|
|
query: `{ nil != nil }`,
|
|
span: &mockSpan{
|
|
attributes: map[Attribute]Static{
|
|
NewAttribute("float"): NewStaticFloat(2.0),
|
|
},
|
|
},
|
|
matches: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
// create a evalTC and use testEvaluator
|
|
tc := evalTC{
|
|
query: tt.query,
|
|
input: []*Spanset{
|
|
{Spans: []Span{tt.span}},
|
|
},
|
|
output: []*Spanset{},
|
|
}
|
|
if tt.matches {
|
|
tc.output = tc.input
|
|
}
|
|
testEvaluator(t, tc)
|
|
}
|
|
}
|
|
|
|
func BenchmarkBinOp(b *testing.B) {
|
|
ops := []struct {
|
|
op BinaryOperation
|
|
}{
|
|
{
|
|
op: BinaryOperation{
|
|
Op: OpEqual,
|
|
LHS: NewStaticInt(1),
|
|
RHS: NewStaticInt(1),
|
|
},
|
|
},
|
|
{
|
|
op: BinaryOperation{
|
|
Op: OpEqual,
|
|
LHS: NewStaticFloat(1),
|
|
RHS: NewStaticInt(1),
|
|
},
|
|
},
|
|
{
|
|
op: BinaryOperation{
|
|
Op: OpEqual,
|
|
LHS: NewStaticDuration(1),
|
|
RHS: NewStaticFloat(1),
|
|
},
|
|
},
|
|
{
|
|
op: BinaryOperation{
|
|
Op: OpEqual,
|
|
LHS: NewStaticFloat(1),
|
|
RHS: NewStaticFloat(1),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, o := range ops {
|
|
b.Run(o.op.String(), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = o.op.execute(&mockSpan{})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkUniquespans benchmarks the performance of the uniqueSpans function using
|
|
// different numbers of spansets and spans.
|
|
func BenchmarkUniqueSpans(b *testing.B) {
|
|
sizes := []int{1, 10, 100, 1000, 10000}
|
|
|
|
for _, lhs := range sizes {
|
|
for i := len(sizes) - 1; i >= 0; i-- {
|
|
rhs := sizes[i]
|
|
b.Run(fmt.Sprintf("%d|%d", rhs, lhs), func(b *testing.B) {
|
|
lhsSpansets := []*Spanset{{Spans: make([]Span, lhs)}}
|
|
rhsSpansets := []*Spanset{{Spans: make([]Span, rhs)}}
|
|
for j := 0; j < lhs; j++ {
|
|
lhsSpansets[0].Spans[j] = &mockSpan{id: []byte{byte(j)}}
|
|
}
|
|
for j := 0; j < rhs; j++ {
|
|
rhsSpansets[0].Spans[j] = &mockSpan{id: []byte{byte(j)}}
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
uniqueSpans(lhsSpansets, rhsSpansets)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAggregate(b *testing.B) {
|
|
agg := newAggregate(aggregateAvg, NewStaticInt(3))
|
|
ss := make([]*Spanset, 1)
|
|
ss[0] = &Spanset{
|
|
Spans: make([]Span, 1000),
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = agg.evaluate(ss)
|
|
}
|
|
}
|
|
|
|
func TestNotParentWithEmptyLHS(t *testing.T) {
|
|
// Build a single spanset with only RHS candidates matching name="list-articles"
|
|
rhsChild := newMockSpan([]byte{1}).WithNestedSetInfo(0, 1, 2)
|
|
rhsChild.attributes[IntrinsicNameAttribute] = NewStaticString("list-articles")
|
|
|
|
ss := []*Spanset{
|
|
{Spans: []Span{rhsChild}},
|
|
}
|
|
|
|
query := "{ span:name = `some-span-that-does-not-exist` } !< { span:name = `list-articles` }"
|
|
ast, err := Parse(query)
|
|
require.NoError(t, err)
|
|
|
|
out, err := ast.Pipeline.evaluate(ss)
|
|
require.NoError(t, err)
|
|
|
|
// Expect the RHS span to be returned because no LHS parent exists (negated parent)
|
|
require.Len(t, out, 1)
|
|
require.Len(t, out[0].Spans, 1)
|
|
nameStatic, _ := out[0].Spans[0].AttributeFor(IntrinsicNameAttribute)
|
|
expected := NewStaticString("list-articles")
|
|
require.True(t, nameStatic.Equals(&expected))
|
|
}
|