blob: 6b8265fc530415611abe8590cde8d1177c311424 [file] [log] [blame]
// Package dfloader implements results.Loader using a DataFrameBuilder.
package dfloader
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/vec32"
"go.skia.org/infra/perf/go/dataframe"
"go.skia.org/infra/perf/go/dataframe/mocks"
perfgit "go.skia.org/infra/perf/go/git"
"go.skia.org/infra/perf/go/git/gittest"
"go.skia.org/infra/perf/go/trybot/results"
"go.skia.org/infra/perf/go/trybot/store"
storeMocks "go.skia.org/infra/perf/go/trybot/store/mocks"
"go.skia.org/infra/perf/go/types"
)
var errFromMock = fmt.Errorf("MockError")
const testTileSize = 4
const e = vec32.MissingDataSentinel
func setupForTest(t *testing.T) (context.Context, *perfgit.Git, []string) {
ctx, db, _, hashes, _, instanceConfig := gittest.NewForTest(t)
instanceConfig.DataStoreConfig.TileSize = testTileSize
g, err := perfgit.New(ctx, true, db, instanceConfig)
require.NoError(t, err)
return ctx, g, hashes
}
func TestLoader_UnknownCommit_ReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
storeMock := &storeMocks.TryBotStore{}
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.Commit,
Query: "config=8888",
CommitNumber: 200, // Not a valid commit.
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "Failed to get details for CommitNumber")
}
func TestLoader_InvalidQuery_ReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
storeMock := &storeMocks.TryBotStore{}
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.Commit,
CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
Query: "%gh&%ij",
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid URL")
}
func TestLoader_EmptyQuery_LoadReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
storeMock := &storeMocks.TryBotStore{}
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.Commit,
Query: "",
CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Equal(t, ErrQueryMustNotBeEmpty, err)
}
func TestLoader_LoadWithDataFrameBuilderThatErrorsNewNFromQuery_LoadReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
dfb.On("NewNFromQuery", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errFromMock)
storeMock := &storeMocks.TryBotStore{}
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.Commit,
Query: "config=8888",
CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), errFromMock.Error())
}
func TestLoader_LoadWithTryBotStoreThatErrors_LoadReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeMock.On("Get", mock.Anything, cl, patch).Return(nil, errFromMock)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), errFromMock.Error())
}
func TestLoader_LoadDataFrameBuilderThatErrorsNewNFromKeys_LoadReturnsError(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errFromMock)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeMock.On("Get", mock.Anything, cl, patch).Return(nil, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
_, err := loader.Load(ctx, request, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), errFromMock.Error())
}
func TestLoader_ZeroLengthResponseFromTryBotStore_LoadReturnsSuccess(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{},
ParamSet: paramtools.NewReadOnlyParamSet(),
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeMock.On("Get", mock.Anything, cl, patch).Return(nil, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
assert.Empty(t, resp.Results)
assert.Empty(t, resp.Header)
assert.Empty(t, resp.ParamSet)
}
func TestLoader_OneTraceTryBotHappyPath_LoadReturnsSuccess(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 0, Timestamp: gittest.StartTime.Unix()},
{Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
{Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
{Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
{Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
{Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
{Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
{Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
{Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
{Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
},
ParamSet: paramtools.ReadOnlyParamSet{"config": []string{"gpu"}},
TraceSet: types.TraceSet{
",config=gpu,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
},
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeResults := []store.GetResult{
{
TraceName: ",config=gpu,",
Value: 3.0,
},
}
storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
expected := results.TryBotResult{
Params: paramtools.Params{"config": "gpu"},
Median: 1,
Lower: 0.1825742,
Upper: 0.122474514,
StdDevRatio: 16.329927,
Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
}
assert.Len(t, resp.Results, 1)
assert.Equal(t, expected, resp.Results[0])
assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
assert.Equal(t, df.ParamSet, resp.ParamSet)
}
func TestLoader_TwoTraces_LoadReturnsSuccessAndResultsAreSorted(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 0, Timestamp: gittest.StartTime.Unix()},
{Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
{Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
{Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
{Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
{Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
{Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
{Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
},
ParamSet: paramtools.ReadOnlyParamSet{"config": []string{"gpu", "cpu"}},
TraceSet: types.TraceSet{
",config=cpu,": []float32{1, 1, 1, 1, 2, 2, 2, 0},
",config=gpu,": []float32{1, 1, 1, 1, 2, 2, 2, 0},
},
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeResults := []store.GetResult{
{
TraceName: ",config=gpu,",
Value: 6.0,
},
{
TraceName: ",config=cpu,",
Value: 4.0,
},
}
storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
assert.Len(t, resp.Results, 2)
expected := []results.TryBotResult{
{
Params: paramtools.Params{"config": "gpu"},
Median: 1,
Lower: 0,
Upper: 1,
StdDevRatio: 5,
Values: []float32{1, 1, 1, 1, 2, 2, 2, 6},
},
{
Params: paramtools.Params{"config": "cpu"},
Median: 1,
Lower: 0,
Upper: 1,
StdDevRatio: 3,
Values: []float32{1, 1, 1, 1, 2, 2, 2, 4},
},
}
assert.Equal(t, expected, resp.Results)
assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
assert.Equal(t, df.ParamSet, resp.ParamSet)
}
func TestLoader_UnknownTracesAreIgnored_LoadReturnsSuccess(t *testing.T) {
ctx, g, _ := setupForTest(t)
// The trybotStore has two results, but there are trace values for only one of those results (config=8888).
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 0, Timestamp: gittest.StartTime.Unix()},
{Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
{Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
{Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
{Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
{Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
{Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
{Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
{Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
{Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
},
ParamSet: paramtools.ReadOnlyParamSet{"config": []string{"565", "8888"}},
TraceSet: types.TraceSet{
",config=8888,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
},
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeResults := []store.GetResult{
{
TraceName: ",config=8888,",
Value: 3.0,
},
{
TraceName: ",config=565,",
Value: 4.0,
},
}
storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
expected := results.TryBotResult{
Params: paramtools.Params{"config": "8888"},
Median: 1,
Lower: 0.1825742,
Upper: 0.122474514,
StdDevRatio: 16.329927,
Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
}
assert.Len(t, resp.Results, 1)
assert.Equal(t, expected, resp.Results[0])
assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
assert.Equal(t, paramtools.ReadOnlyParamSet{"config": []string{"8888"}}, resp.ParamSet)
}
func TestLoader_InsufficientNonMissingDataSentinel_ResultIsSkipped(t *testing.T) {
ctx, g, _ := setupForTest(t)
// The trybotStore has two results, but there are trace values for only one of those results (config=8888).
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 0, Timestamp: gittest.StartTime.Unix()},
{Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
{Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
{Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
{Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
{Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
{Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
{Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
{Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
{Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
},
ParamSet: paramtools.ReadOnlyParamSet{"config": []string{"565", "8888"}},
TraceSet: types.TraceSet{
",config=8888,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
",config=565,": []float32{e, e, e, e, e, e, e, e, e, 1.2}, // Should be dropped from results since there isn't enough valid data.
},
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeResults := []store.GetResult{
{
TraceName: ",config=8888,",
Value: 3.0,
},
{
TraceName: ",config=565,",
Value: 4.0,
},
}
storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
expected := results.TryBotResult{
Params: paramtools.Params{"config": "8888"},
Median: 1,
Lower: 0.1825742,
Upper: 0.122474514,
StdDevRatio: 16.329927,
Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
}
assert.Len(t, resp.Results, 1)
assert.Equal(t, expected, resp.Results[0])
assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
assert.Equal(t, paramtools.ReadOnlyParamSet{"config": []string{"8888"}}, resp.ParamSet)
}
func TestLoader_InvalidTraceKeysAreIgnored_LoadReturnsSuccess(t *testing.T) {
ctx, g, _ := setupForTest(t)
dfb := &mocks.DataFrameBuilder{}
df := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{},
ParamSet: paramtools.ReadOnlyParamSet{},
TraceSet: types.TraceSet{
"this-isnt-a-valid-key": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
},
}
dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
storeMock := &storeMocks.TryBotStore{}
const cl = types.CL("123456")
const patch = int(1)
storeResults := []store.GetResult{
{
TraceName: ",config=gpu,",
Value: 3.0,
},
}
storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
loader := New(dfb, storeMock, g)
request := results.TryBotRequest{
Kind: results.TryBot,
CL: cl,
PatchNumber: patch,
}
resp, err := loader.Load(ctx, request, nil)
require.NoError(t, err)
assert.Empty(t, resp.Results)
assert.Empty(t, resp.Header)
assert.Empty(t, resp.ParamSet)
}