The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
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.
 
 
 
 
 
 
grafana/pkg/promlib/querydata/framing_test.go

154 lines
4.1 KiB

package querydata_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/promlib/models"
)
var update = true
func TestRangeResponses(t *testing.T) {
tt := []struct {
name string
filepath string
}{
{name: "parse a simple matrix response", filepath: "range_simple"},
{name: "parse a simple matrix response with value missing steps", filepath: "range_missing"},
{name: "parse a matrix response with Infinity", filepath: "range_infinity"},
{name: "parse a matrix response with NaN", filepath: "range_nan"},
{name: "parse a response with legendFormat __auto", filepath: "range_auto"},
}
for _, test := range tt {
queryFileName := filepath.Join("../testdata", test.filepath+".query.json")
responseFileName := filepath.Join("../testdata", test.filepath+".result.json")
goldenFileName := test.filepath + ".result.golden"
t.Run(test.name, goldenScenario(test.name, queryFileName, responseFileName, goldenFileName))
}
}
func TestExemplarResponses(t *testing.T) {
tt := []struct {
name string
filepath string
}{
{name: "parse an exemplar response", filepath: "exemplar"},
}
for _, test := range tt {
queryFileName := filepath.Join("../testdata", test.filepath+".query.json")
responseFileName := filepath.Join("../testdata", test.filepath+".result.json")
goldenFileName := test.filepath + ".result.golden"
t.Run(test.name, goldenScenario(test.name, queryFileName, responseFileName, goldenFileName))
}
}
func goldenScenario(name, queryFileName, responseFileName, goldenFileName string) func(t *testing.T) {
return func(t *testing.T) {
query, err := loadStoredQuery(queryFileName)
require.NoError(t, err)
//nolint:gosec
responseBytes, err := os.ReadFile(responseFileName)
require.NoError(t, err)
result, err := runQuery(responseBytes, query)
require.NoError(t, err)
require.Len(t, result.Responses, 1)
dr, found := result.Responses["A"]
require.True(t, found)
experimental.CheckGoldenJSONResponse(t, "../testdata", goldenFileName, &dr, update)
}
}
// we store the prometheus query data in a json file, here is some minimal code
// to be able to read it back. unfortunately we cannot use the models.Query
// struct here, because it has `time.time` and `time.duration` fields that
// cannot be unmarshalled from JSON automatically.
type storedPrometheusQuery struct {
RefId string
RangeQuery bool
ExemplarQuery bool
Start int64
End int64
Step int64
Expr string
LegendFormat string
}
func loadStoredQuery(fileName string) (*backend.QueryDataRequest, error) {
//nolint:gosec
bytes, err := os.ReadFile(fileName)
if err != nil {
return nil, err
}
var sq storedPrometheusQuery
err = json.Unmarshal(bytes, &sq)
if err != nil {
return nil, err
}
qm := models.QueryModel{
PrometheusQueryProperties: models.PrometheusQueryProperties{
Range: sq.RangeQuery,
Exemplar: sq.ExemplarQuery,
Expr: sq.Expr,
LegendFormat: sq.LegendFormat,
},
CommonQueryProperties: sdkapi.CommonQueryProperties{
IntervalMS: float64(sq.Step * 1000),
},
Interval: fmt.Sprintf("%ds", sq.Step),
}
data, err := json.Marshal(&qm)
if err != nil {
return nil, err
}
return &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
TimeRange: backend.TimeRange{
From: time.Unix(sq.Start, 0),
To: time.Unix(sq.End, 0),
},
RefID: sq.RefId,
Interval: time.Second * time.Duration(sq.Step),
JSON: json.RawMessage(data),
},
},
}, nil
}
func runQuery(response []byte, q *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
tCtx, err := setup()
if err != nil {
return nil, err
}
res := &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewReader(response)),
}
tCtx.httpProvider.setResponse(res)
return tCtx.queryData.Execute(context.Background(), q)
}