mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
12 KiB
493 lines
12 KiB
package dataobj
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/loki/v3/pkg/dataobj/internal/dataset"
|
|
"github.com/grafana/loki/v3/pkg/dataobj/internal/sections/streams"
|
|
)
|
|
|
|
type fakeColumn struct{ dataset.Column }
|
|
|
|
var (
|
|
fakeMinColumn = &fakeColumn{}
|
|
fakeMaxColumn = &fakeColumn{}
|
|
)
|
|
|
|
func TestMatchStreamsTimeRangePredicate(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
stream streams.Stream
|
|
pred Predicate
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "stream fully inside range inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(1 * time.Hour),
|
|
MaxTimestamp: now.Add(2 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream fully inside range exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(1 * time.Hour),
|
|
MaxTimestamp: now.Add(2 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream overlaps start inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(-1 * time.Hour),
|
|
MaxTimestamp: now.Add(1 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream overlaps start exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(-1 * time.Hour),
|
|
MaxTimestamp: now.Add(1 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream overlaps end inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(2 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream overlaps end exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(2 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream encompasses range inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(-1 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream encompasses range exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(-1 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream before range inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(-2 * time.Hour),
|
|
MaxTimestamp: now.Add(-1 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "stream after range inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(4 * time.Hour),
|
|
MaxTimestamp: now.Add(5 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "stream exactly at start inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now,
|
|
MaxTimestamp: now.Add(1 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream exactly at start exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now,
|
|
MaxTimestamp: now.Add(1 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream exactly at end inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(2 * time.Hour),
|
|
MaxTimestamp: now.Add(3 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream exactly at end exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(2 * time.Hour),
|
|
MaxTimestamp: now.Add(3 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream end at start inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(1 * time.Hour),
|
|
MaxTimestamp: now.Add(2 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now.Add(2 * time.Hour),
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream end at start exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(1 * time.Hour),
|
|
MaxTimestamp: now.Add(2 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now.Add(2 * time.Hour),
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "stream start at end inclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(3 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now.Add(2 * time.Hour),
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "stream start at end exclusive",
|
|
stream: streams.Stream{
|
|
MinTimestamp: now.Add(3 * time.Hour),
|
|
MaxTimestamp: now.Add(4 * time.Hour),
|
|
},
|
|
pred: TimeRangePredicate[StreamsPredicate]{
|
|
StartTime: now.Add(2 * time.Hour),
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
predicate := convertStreamsTimePredicate(tt.pred.(TimeRangePredicate[StreamsPredicate]), fakeMinColumn, fakeMaxColumn)
|
|
result := evaluateStreamsPredicate(predicate, tt.stream)
|
|
require.Equal(t, tt.expected, result, "matchStreamsPredicate returned unexpected result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func evaluateStreamsPredicate(p dataset.Predicate, s streams.Stream) bool {
|
|
switch p := p.(type) {
|
|
case dataset.AndPredicate:
|
|
return evaluateStreamsPredicate(p.Left, s) && evaluateStreamsPredicate(p.Right, s)
|
|
case dataset.OrPredicate:
|
|
return evaluateStreamsPredicate(p.Left, s) || evaluateStreamsPredicate(p.Right, s)
|
|
case dataset.NotPredicate:
|
|
return !evaluateStreamsPredicate(p.Inner, s)
|
|
case dataset.GreaterThanPredicate:
|
|
switch p.Column {
|
|
case fakeMinColumn:
|
|
return s.MinTimestamp.After(time.Unix(0, p.Value.Int64()).UTC())
|
|
case fakeMaxColumn:
|
|
return s.MaxTimestamp.After(time.Unix(0, p.Value.Int64()).UTC())
|
|
}
|
|
panic("unexpected column")
|
|
|
|
case dataset.LessThanPredicate:
|
|
switch p.Column {
|
|
case fakeMinColumn:
|
|
return s.MinTimestamp.Before(time.Unix(0, p.Value.Int64()).UTC())
|
|
case fakeMaxColumn:
|
|
return s.MaxTimestamp.Before(time.Unix(0, p.Value.Int64()).UTC())
|
|
}
|
|
panic("unexpected column")
|
|
|
|
default:
|
|
panic("unexpected predicate")
|
|
}
|
|
}
|
|
|
|
func TestMatchTimestamp(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ts time.Time
|
|
pred TimeRangePredicate[LogsPredicate]
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "timestamp inside range inclusive",
|
|
ts: now.Add(1 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp inside range exclusive",
|
|
ts: now.Add(1 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp before range inclusive",
|
|
ts: now.Add(-1 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "timestamp after range inclusive",
|
|
ts: now.Add(4 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "timestamp exactly at start inclusive",
|
|
ts: now,
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp exactly at start exclusive",
|
|
ts: now,
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "timestamp exactly at end inclusive",
|
|
ts: now.Add(3 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp exactly at end exclusive",
|
|
ts: now.Add(3 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "timestamp exactly at both bounds inclusive",
|
|
ts: now,
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now,
|
|
IncludeStart: true,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp exactly at both bounds exclusive",
|
|
ts: now,
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now,
|
|
IncludeStart: false,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "timestamp exactly at start with mixed bounds",
|
|
ts: now,
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: true,
|
|
IncludeEnd: false,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "timestamp exactly at end with mixed bounds",
|
|
ts: now.Add(3 * time.Hour),
|
|
pred: TimeRangePredicate[LogsPredicate]{
|
|
StartTime: now,
|
|
EndTime: now.Add(3 * time.Hour),
|
|
IncludeStart: false,
|
|
IncludeEnd: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
predicate := convertLogsTimePredicate(tt.pred, nil)
|
|
result := evaluateRecordPredicate(predicate, tt.ts)
|
|
require.Equal(t, tt.expected, result, "matchTimestamp returned unexpected result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func evaluateRecordPredicate(p dataset.Predicate, ts time.Time) bool {
|
|
switch p := p.(type) {
|
|
case dataset.AndPredicate:
|
|
return evaluateRecordPredicate(p.Left, ts) && evaluateRecordPredicate(p.Right, ts)
|
|
case dataset.OrPredicate:
|
|
return evaluateRecordPredicate(p.Left, ts) || evaluateRecordPredicate(p.Right, ts)
|
|
case dataset.NotPredicate:
|
|
return !evaluateRecordPredicate(p.Inner, ts)
|
|
case dataset.GreaterThanPredicate:
|
|
return ts.After(time.Unix(0, p.Value.Int64()))
|
|
case dataset.LessThanPredicate:
|
|
return ts.Before(time.Unix(0, p.Value.Int64()))
|
|
case dataset.EqualPredicate:
|
|
return ts.Equal(time.Unix(0, p.Value.Int64()))
|
|
|
|
default:
|
|
panic("unexpected predicate")
|
|
}
|
|
}
|
|
|