grafana/pkg/tsdb/influxdb/flux/executor_test.go
Gábor Farkas ab26c4dfa4
InfluxDB: Flux: handle boolean data-columns (#34479)
* InfluxDB: Flux: handle boolean data-columns

* normalize data-type strings based on documentation

* reference missing data type
2021-05-25 17:22:04 +02:00

307 lines
10 KiB
Go

package flux
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
)
//--------------------------------------------------------------
// TestData -- reads result from saved files
//--------------------------------------------------------------
// MockRunner reads local file path for testdata.
type MockRunner struct {
testDataPath string
}
func (r *MockRunner) runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) {
bytes, err := ioutil.ReadFile(filepath.Join("testdata", r.testDataPath))
if err != nil {
return nil, err
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond)
if r.Method == http.MethodPost {
w.WriteHeader(http.StatusOK)
_, err := w.Write(bytes)
if err != nil {
panic(fmt.Sprintf("Failed to write response: %s", err))
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
client := influxdb2.NewClient(server.URL, "a")
return client.QueryAPI("x").Query(ctx, q)
}
func executeMockedQuery(t *testing.T, name string, query queryModel) *backend.DataResponse {
runner := &MockRunner{
testDataPath: name + ".csv",
}
dr := executeQuery(context.Background(), query, runner, 50)
return &dr
}
func verifyGoldenResponse(t *testing.T, name string) *backend.DataResponse {
dr := executeMockedQuery(t, name, queryModel{MaxDataPoints: 100})
err := experimental.CheckGoldenDataResponse(filepath.Join("testdata", fmt.Sprintf("%s.golden.txt", name)),
dr, true)
require.NoError(t, err)
require.NoError(t, dr.Error)
return dr
}
func TestExecuteSimple(t *testing.T) {
dr := verifyGoldenResponse(t, "simple")
require.Len(t, dr.Frames, 1)
require.Contains(t, dr.Frames[0].Name, "test")
require.Len(t, dr.Frames[0].Fields[1].Labels, 2)
require.Equal(t, "_time", dr.Frames[0].Fields[0].Name)
st, err := dr.Frames[0].StringTable(-1, -1)
require.NoError(t, err)
fmt.Println(st)
fmt.Println("----------------------")
}
func TestExecuteSingle(t *testing.T) {
dr := verifyGoldenResponse(t, "single")
require.Len(t, dr.Frames, 1)
}
func TestExecuteMultiple(t *testing.T) {
dr := verifyGoldenResponse(t, "multiple")
require.Len(t, dr.Frames, 3)
require.Contains(t, dr.Frames[0].Name, "test")
require.Len(t, dr.Frames[0].Fields[1].Labels, 2)
require.Equal(t, "_time", dr.Frames[0].Fields[0].Name)
st, err := dr.Frames[0].StringTable(-1, -1)
require.NoError(t, err)
fmt.Println(st)
fmt.Println("----------------------")
}
func TestExecuteColumnNamedTable(t *testing.T) {
dr := verifyGoldenResponse(t, "table")
require.Len(t, dr.Frames, 1)
}
func TestExecuteGrouping(t *testing.T) {
dr := verifyGoldenResponse(t, "grouping")
require.Len(t, dr.Frames, 3)
require.Contains(t, dr.Frames[0].Name, "system")
require.Len(t, dr.Frames[0].Fields[0].Labels, 1)
require.Equal(t, "_time", dr.Frames[0].Fields[1].Name)
st, err := dr.Frames[0].StringTable(-1, -1)
require.NoError(t, err)
fmt.Println(st)
fmt.Println("----------------------")
}
func TestAggregateGrouping(t *testing.T) {
dr := verifyGoldenResponse(t, "aggregate")
require.Len(t, dr.Frames, 1)
str, err := dr.Frames[0].StringTable(-1, -1)
require.NoError(t, err)
fmt.Println(str)
// `Name:
// Dimensions: 2 Fields by 3 Rows
// +-------------------------------+--------------------------+
// | Name: _time | Name: _value |
// | Labels: | Labels: host=hostname.ru |
// | Type: []*time.Time | Type: []*float64 |
// +-------------------------------+--------------------------+
// | 2020-06-05 12:06:00 +0000 UTC | 8.291 |
// | 2020-06-05 12:07:00 +0000 UTC | 0.534 |
// | 2020-06-05 12:08:00 +0000 UTC | 0.667 |
// +-------------------------------+--------------------------+
// `
t1 := time.Date(2020, 6, 5, 12, 6, 0, 0, time.UTC)
t2 := time.Date(2020, 6, 5, 12, 7, 0, 0, time.UTC)
t3 := time.Date(2020, 6, 5, 12, 8, 0, 0, time.UTC)
expectedFrame := data.NewFrame("",
data.NewField("_time", nil, []*time.Time{&t1, &t2, &t3}),
data.NewField("_value", map[string]string{"host": "hostname.ru"}, []*float64{
pointer.Float64(8.291),
pointer.Float64(0.534),
pointer.Float64(0.667),
}),
)
expectedFrame.Meta = &data.FrameMeta{}
diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...)
assert.Empty(t, diff)
}
func TestNonStandardTimeColumn(t *testing.T) {
dr := verifyGoldenResponse(t, "non_standard_time_column")
require.Len(t, dr.Frames, 1)
str, err := dr.Frames[0].StringTable(-1, -1)
require.NoError(t, err)
fmt.Println(str)
// Dimensions: 3 Fields by 1 Rows
// +-----------------------------------------+-----------------------------------------+------------------+
// | Name: _start_water | Name: _stop_water | Name: _value |
// | Labels: st=1 | Labels: st=1 | Labels: st=1 |
// | Type: []*time.Time | Type: []*time.Time | Type: []*float64 |
// +-----------------------------------------+-----------------------------------------+------------------+
// | 2020-06-28 17:50:13.012584046 +0000 UTC | 2020-06-29 17:50:13.012584046 +0000 UTC | 156.304 |
// +-----------------------------------------+-----------------------------------------+------------------+
t1 := time.Date(2020, 6, 28, 17, 50, 13, 12584046, time.UTC)
t2 := time.Date(2020, 6, 29, 17, 50, 13, 12584046, time.UTC)
expectedFrame := data.NewFrame("",
data.NewField("_start_water", map[string]string{"st": "1"}, []*time.Time{&t1}),
data.NewField("_stop_water", map[string]string{"st": "1"}, []*time.Time{&t2}),
data.NewField("_value", map[string]string{"st": "1"}, []*float64{
pointer.Float64(156.304),
}),
)
expectedFrame.Meta = &data.FrameMeta{}
diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...)
assert.Empty(t, diff)
}
func TestBuckets(t *testing.T) {
verifyGoldenResponse(t, "buckets")
}
func TestBooleanTagGrouping(t *testing.T) {
verifyGoldenResponse(t, "boolean_tag")
}
func TestBooleanData(t *testing.T) {
verifyGoldenResponse(t, "boolean_data")
}
func TestGoldenFiles(t *testing.T) {
verifyGoldenResponse(t, "renamed")
}
func TestRealQuery(t *testing.T) {
t.Skip() // this is used for local testing
t.Run("Check buckets() query on localhost", func(t *testing.T) {
json := simplejson.New()
json.Set("organization", "test-org")
dsInfo := &models.DataSource{
Url: "http://localhost:9999", // NOTE! no api/v2
JsonData: json,
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"token": "PjSEcM5oWhqg2eI6IXcqYJFe5UbMM_xt-UNlAL0BRYJqLeVpcdMWidiPfWxGhu4Xrh6wioRR-CiadCg-ady68Q==",
}),
}
runner, err := runnerFromDataSource(httpclient.NewProvider(), dsInfo)
require.NoError(t, err)
dr := executeQuery(context.Background(), queryModel{
MaxDataPoints: 100,
RawQuery: "buckets()",
}, runner, 50)
err = experimental.CheckGoldenDataResponse(filepath.Join("testdata", "buckets-real.golden.txt"), &dr, true)
require.NoError(t, err)
})
}
func assertDataResponseDimensions(t *testing.T, dr *backend.DataResponse, rows int, columns int) {
require.Len(t, dr.Frames, 1)
fields := dr.Frames[0].Fields
require.Len(t, fields, rows)
require.Equal(t, fields[0].Len(), columns)
require.Equal(t, fields[1].Len(), columns)
}
func TestMaxDataPointsExceededNoAggregate(t *testing.T) {
// unfortunately the golden-response style tests do not support
// responses that contain errors, so we can only do manual checks
// on the DataResponse
dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{MaxDataPoints: 2})
// it should contain the error-message
require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2. Try using the aggregateWindow() function in your query to reduce the number of points returned.")
assertDataResponseDimensions(t, dr, 2, 21)
}
func TestMaxDataPointsExceededWithAggregate(t *testing.T) {
// unfortunately the golden-response style tests do not support
// responses that contain errors, so we can only do manual checks
// on the DataResponse
dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{RawQuery: "aggregateWindow()", MaxDataPoints: 2})
// it should contain the error-message
require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2.")
assertDataResponseDimensions(t, dr, 2, 21)
}
func TestMultivalue(t *testing.T) {
// we await a non-labeled _time column
// and two value-columns named _value and _value2
dr := verifyGoldenResponse(t, "multivalue")
require.Len(t, dr.Frames, 4)
frame := dr.Frames[0]
require.Len(t, frame.Fields, 3)
require.Equal(t, frame.Fields[0].Name, "_time")
require.Equal(t, frame.Fields[0].Len(), 2)
require.Len(t, frame.Fields[0].Labels, 0)
require.Equal(t, frame.Fields[1].Name, "_value")
require.Len(t, frame.Fields[1].Labels, 5)
require.Equal(t, frame.Fields[2].Name, "_value2")
require.Len(t, frame.Fields[2].Labels, 5)
}
func TestMultiTime(t *testing.T) {
// we await three columns, _time, _time2, _value
// all have all labels
dr := verifyGoldenResponse(t, "multitime")
require.Len(t, dr.Frames, 4)
frame := dr.Frames[0]
require.Len(t, frame.Fields, 3)
require.Equal(t, frame.Fields[0].Name, "_time")
require.Equal(t, frame.Fields[0].Len(), 1)
require.Len(t, frame.Fields[0].Labels, 5)
require.Equal(t, frame.Fields[1].Name, "_time2")
require.Len(t, frame.Fields[1].Labels, 5)
require.Equal(t, frame.Fields[2].Name, "_value")
require.Len(t, frame.Fields[2].Labels, 5)
}