Files
traces/integration/api/combine_test.go
T
Joe ElliottandGitHub 863b79ba21 Tempo 3.0: Integration Tests Rewrite (#6088)
* First pass test harness

Signed-off-by: Joe Elliott <number101010@gmail.com>

* limits - passed

Signed-off-by: Joe Elliott <number101010@gmail.com>

* metrics generator - passed

Signed-off-by: Joe Elliott <number101010@gmail.com>

* harness cleanup/fix target info test

Signed-off-by: Joe Elliott <number101010@gmail.com>

* deployments - passed

Signed-off-by: Joe Elliott <number101010@gmail.com>

* receivers - passed

Signed-off-by: Joe Elliott <number101010@gmail.com>

* harness cleanup

Signed-off-by: Joe Elliott <number101010@gmail.com>

* storage - passed

Signed-off-by: Joe Elliott <number101010@gmail.com>

* api - passing

Signed-off-by: Joe Elliott <number101010@gmail.com>

* cleanup and CI

Signed-off-by: Joe Elliott <number101010@gmail.com>

* Additional tests

Signed-off-by: Joe Elliott <number101010@gmail.com>

* it's me and you linter. how many times are you going to do this to me?

Signed-off-by: Joe Elliott <number101010@gmail.com>

* lint and tests

Signed-off-by: Joe Elliott <number101010@gmail.com>

* restored live store partition tests

Signed-off-by: Joe Elliott <number101010@gmail.com>

* massive lint

Signed-off-by: Joe Elliott <number101010@gmail.com>

* cleanup

Signed-off-by: Joe Elliott <number101010@gmail.com>

* lint >.<

Signed-off-by: Joe Elliott <number101010@gmail.com>

* wups

Signed-off-by: Joe Elliott <number101010@gmail.com>

* cleanup and stabilizing

Signed-off-by: Joe Elliott <number101010@gmail.com>

* cleanup

Signed-off-by: Joe Elliott <number101010@gmail.com>

* omg

Signed-off-by: Joe Elliott <number101010@gmail.com>

* added config file. removed empty file. deleted some debug code

Signed-off-by: Joe Elliott <number101010@gmail.com>

* even more fixes

Signed-off-by: Joe Elliott <number101010@gmail.com>

* maybe this works? maybe it was all local networking issues

Signed-off-by: Joe Elliott <number101010@gmail.com>

* review

Signed-off-by: Joe Elliott <number101010@gmail.com>

* backed off huge write timeout. added missing config file

Signed-off-by: Joe Elliott <number101010@gmail.com>

* restore disable parallelism setting. restore beefy write timeout

Signed-off-by: Joe Elliott <number101010@gmail.com>

* apparently another test relied on the http receiver

Signed-off-by: Joe Elliott <number101010@gmail.com>

* livestore avail zone!

Signed-off-by: Joe Elliott <number101010@gmail.com>

* add support for v1 traces and tags endpoints

Signed-off-by: Joe Elliott <number101010@gmail.com>

* review

Signed-off-by: Joe Elliott <number101010@gmail.com>

* changed the location of the changelog entry to avoid conflicts

Signed-off-by: Joe Elliott <number101010@gmail.com>

---------

Signed-off-by: Joe Elliott <number101010@gmail.com>
2026-01-07 08:31:11 -05:00

321 lines
8.3 KiB
Go

package api
import (
"testing"
"github.com/grafana/tempo/pkg/tempopb"
v1 "github.com/grafana/tempo/pkg/tempopb/common/v1"
"github.com/stretchr/testify/require"
)
func TestNaiveTagValuesV2Combine(t *testing.T) {
tests := []struct {
name string
rNew *tempopb.SearchTagValuesV2Response
rInto *tempopb.SearchTagValuesV2Response
expected *tempopb.SearchTagValuesV2Response
}{
{
name: "combine unique tag values",
rNew: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value1"},
{Type: "string", Value: "value2"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
},
},
rInto: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value3"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
},
},
expected: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value3"},
{Type: "string", Value: "value1"},
{Type: "string", Value: "value2"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 150,
CompletedJobs: 2,
},
},
},
{
name: "skip duplicate tag values",
rNew: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value1"},
{Type: "string", Value: "value2"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
},
},
rInto: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value1"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
},
},
expected: &tempopb.SearchTagValuesV2Response{
TagValues: []*tempopb.TagValue{
{Type: "string", Value: "value1"},
{Type: "string", Value: "value2"},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 150,
CompletedJobs: 2,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
naiveTagValuesV2Combine(tt.rNew, tt.rInto)
require.Equal(t, tt.expected, tt.rInto)
})
}
}
func TestNaiveTagsV2Combine(t *testing.T) {
tests := []struct {
name string
rNew *tempopb.SearchTagsV2Response
rInto *tempopb.SearchTagsV2Response
expected int // expected number of unique tags across all scopes
}{
{
name: "combine unique tags from different scopes",
rNew: &tempopb.SearchTagsV2Response{
Scopes: []*tempopb.SearchTagsV2Scope{
{
Name: "resource",
Tags: []string{"tag1", "tag2"},
},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
},
},
rInto: &tempopb.SearchTagsV2Response{
Scopes: []*tempopb.SearchTagsV2Scope{
{
Name: "span",
Tags: []string{"tag3"},
},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
},
},
expected: 3, // tag1, tag2, tag3
},
{
name: "deduplicate tags within same scope",
rNew: &tempopb.SearchTagsV2Response{
Scopes: []*tempopb.SearchTagsV2Scope{
{
Name: "resource",
Tags: []string{"tag1", "tag2"},
},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
},
},
rInto: &tempopb.SearchTagsV2Response{
Scopes: []*tempopb.SearchTagsV2Scope{
{
Name: "resource",
Tags: []string{"tag1", "tag3"},
},
},
Metrics: &tempopb.MetadataMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
},
},
expected: 3, // tag1, tag2, tag3
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
naiveTagsV2Combine(tt.rNew, tt.rInto)
// Count total unique tags across all scopes
totalTags := 0
for _, scope := range tt.rInto.Scopes {
totalTags += len(scope.Tags)
}
require.Equal(t, tt.expected, totalTags)
// Verify metrics were combined
expectedBytes := tt.rNew.Metrics.InspectedBytes + 50 // original rInto value
expectedJobs := tt.rNew.Metrics.CompletedJobs + 1 // original rInto value
require.Equal(t, expectedBytes, tt.rInto.Metrics.InspectedBytes)
require.Equal(t, expectedJobs, tt.rInto.Metrics.CompletedJobs)
})
}
}
func TestNaiveQueryRangeCombine(t *testing.T) {
tests := []struct {
name string
rNew *tempopb.QueryRangeResponse
rInto *tempopb.QueryRangeResponse
expected *tempopb.QueryRangeResponse
}{
{
name: "combine series with different labels",
rNew: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "foo", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "bar"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 1000, Value: 1.0}},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
InspectedTraces: 10,
InspectedSpans: 50,
},
},
rInto: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "baz", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "qux"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 2000, Value: 2.0}},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
InspectedTraces: 5,
InspectedSpans: 25,
},
},
expected: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "baz", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "qux"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 2000, Value: 2.0}},
},
{
Labels: []v1.KeyValue{
{Key: "foo", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "bar"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 1000, Value: 1.0}},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 150,
CompletedJobs: 2,
InspectedTraces: 15,
InspectedSpans: 75,
},
},
},
{
name: "combine series with same labels",
rNew: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "foo", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "bar"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 2000, Value: 2.0}},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 100,
CompletedJobs: 1,
InspectedTraces: 10,
InspectedSpans: 50,
},
},
rInto: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "foo", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "bar"}}},
},
Samples: []tempopb.Sample{{TimestampMs: 1000, Value: 1.0}},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 50,
CompletedJobs: 1,
InspectedTraces: 5,
InspectedSpans: 25,
},
},
expected: &tempopb.QueryRangeResponse{
Series: []*tempopb.TimeSeries{
{
Labels: []v1.KeyValue{
{Key: "foo", Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: "bar"}}},
},
Samples: []tempopb.Sample{
{TimestampMs: 1000, Value: 1.0},
{TimestampMs: 2000, Value: 2.0},
},
},
},
Metrics: &tempopb.SearchMetrics{
InspectedBytes: 150,
CompletedJobs: 2,
InspectedTraces: 15,
InspectedSpans: 75,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
naiveQueryRangeCombine(tt.rNew, tt.rInto)
require.Equal(t, tt.expected.Metrics, tt.rInto.Metrics)
require.Len(t, tt.rInto.Series, len(tt.expected.Series))
// Build a map of expected series by label key for comparison
expectedMap := map[string]*tempopb.TimeSeries{}
for _, series := range tt.expected.Series {
expectedMap[keyFromLabels(series.Labels)] = series
}
// Check that all series match expected (order doesn't matter due to map iteration)
for _, series := range tt.rInto.Series {
key := keyFromLabels(series.Labels)
expectedSeries, ok := expectedMap[key]
require.True(t, ok, "unexpected series with key %s", key)
require.Equal(t, expectedSeries.Labels, series.Labels)
require.Len(t, series.Samples, len(expectedSeries.Samples))
}
})
}
}