blob: 68fc8a377d7934775a05fb2a828729aefa6a1053 [file]
package api
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"go.skia.org/infra/perf/go/config"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
anomalyMocks "go.skia.org/infra/perf/go/anomalies/mock"
"go.skia.org/infra/perf/go/chromeperf"
"go.skia.org/infra/perf/go/dataframe"
dataframeMocks "go.skia.org/infra/perf/go/dataframe/mocks"
gitMocks "go.skia.org/infra/perf/go/git/mocks"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/types"
)
func TestTraceValuesHandler_Success(t *testing.T) {
config.Config = &config.InstanceConfig{}
w := httptest.NewRecorder()
req := TraceValuesRequest{
Ids: []string{"trace1"},
MinCommit: 100,
MaxCommit: 200,
}
body, _ := json.Marshal(req)
r := httptest.NewRequest("POST", "/_/trace_values", bytes.NewReader(body))
dfBuilder := dataframeMocks.NewDataFrameBuilder(t)
perfGit := gitMocks.NewGit(t)
api := NewTraceValuesApi(dfBuilder, perfGit, nil, nil)
// Mock Git calls
perfGit.On("CommitNumberFromTime", mock.Anything, time.Time{}).Return(types.CommitNumber(200), nil)
perfGit.On("CommitFromCommitNumber", mock.MatchedBy(func(ctx context.Context) bool {
_, ok := ctx.Deadline()
return ok
}), types.CommitNumber(100)).Return(provider.Commit{Timestamp: 1000}, nil)
perfGit.On("CommitFromCommitNumber", mock.MatchedBy(func(ctx context.Context) bool {
_, ok := ctx.Deadline()
return ok
}), types.CommitNumber(200)).Return(provider.Commit{Timestamp: 2000}, nil)
// Mock DataFrameBuilder calls
fakeDf := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 100, Timestamp: 1000},
{Offset: 200, Timestamp: 2000},
},
TraceSet: types.TraceSet{
"trace1": []float32{1.0, 2.0},
},
}
dfBuilder.On("NewFromKeysAndRange", mock.MatchedBy(func(ctx context.Context) bool {
_, ok := ctx.Deadline()
return ok
}), []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0), mock.Anything).Return(fakeDf, nil)
api.traceValuesHandler(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
var resp TraceValuesResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Results, 1)
require.Len(t, resp.Results["trace1"], 2)
require.Equal(t, int64(100), resp.Results["trace1"][0].CommitNumber)
require.Equal(t, float32(1.0), resp.Results["trace1"][0].Val)
}
func TestTraceValuesHandler_WithTimestamps(t *testing.T) {
w := httptest.NewRecorder()
req := TraceValuesRequest{
Ids: []string{"trace1"},
Begin: 1000,
End: 2000,
}
body, _ := json.Marshal(req)
r := httptest.NewRequest("POST", "/_/trace_values", bytes.NewReader(body))
dfBuilder := dataframeMocks.NewDataFrameBuilder(t)
perfGit := gitMocks.NewGit(t)
api := NewTraceValuesApi(dfBuilder, perfGit, nil, nil)
fakeDf := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 100, Timestamp: 1000},
{Offset: 200, Timestamp: 2000},
},
TraceSet: types.TraceSet{
"trace1": []float32{1.0, 2.0},
},
}
dfBuilder.On("NewFromKeysAndRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0), mock.Anything).Return(fakeDf, nil)
api.traceValuesHandler(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
var resp TraceValuesResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Results, 1)
require.Len(t, resp.Results["trace1"], 2)
}
func TestTraceValuesHandler_WithAnomalies(t *testing.T) {
config.Config = &config.InstanceConfig{
FetchAnomaliesFromSql: true,
}
w := httptest.NewRecorder()
req := TraceValuesRequest{
Ids: []string{"trace1"},
Begin: 1000,
End: 2000,
}
body, _ := json.Marshal(req)
r := httptest.NewRequest("POST", "/_/trace_values", bytes.NewReader(body))
dfBuilder := dataframeMocks.NewDataFrameBuilder(t)
perfGit := gitMocks.NewGit(t)
anomalyStore := &anomalyMocks.Store{}
api := NewTraceValuesApi(dfBuilder, perfGit, anomalyStore, nil)
fakeDf := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 100, Timestamp: 1000},
{Offset: 200, Timestamp: 2000},
},
TraceSet: types.TraceSet{
"trace1": []float32{1.0, 2.0},
},
}
dfBuilder.On("NewFromKeysAndRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0), mock.Anything).Return(fakeDf, nil)
fakeAnomalyMap := chromeperf.AnomalyMap{
"trace1": {
types.CommitNumber(100): {Id: "anomaly1", State: "untriaged"},
},
}
anomalyStore.On("GetAnomaliesInTimeRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0)).Return(fakeAnomalyMap, nil)
api.traceValuesHandler(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
var resp TraceValuesResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Results, 1)
require.NotNil(t, resp.AnomalyMap)
require.Equal(t, "anomaly1", resp.AnomalyMap["trace1"][100].Id)
}
func TestTraceValuesHandler_CappedMaxCommit(t *testing.T) {
config.Config = &config.InstanceConfig{}
w := httptest.NewRecorder()
req := TraceValuesRequest{
Ids: []string{"trace1"},
MinCommit: 100,
MaxCommit: 200,
}
body, _ := json.Marshal(req)
r := httptest.NewRequest("POST", "/_/trace_values", bytes.NewReader(body))
dfBuilder := dataframeMocks.NewDataFrameBuilder(t)
perfGit := gitMocks.NewGit(t)
api := NewTraceValuesApi(dfBuilder, perfGit, nil, nil)
// Mock Git calls
perfGit.On("CommitFromCommitNumber", mock.Anything, types.CommitNumber(100)).Return(provider.Commit{Timestamp: 1000}, nil)
// Mock CommitNumberFromTime to return 150 as most recent
perfGit.On("CommitNumberFromTime", mock.Anything, time.Time{}).Return(types.CommitNumber(150), nil)
// Mock CommitFromCommitNumber for the capped value 150
perfGit.On("CommitFromCommitNumber", mock.Anything, types.CommitNumber(150)).Return(provider.Commit{Timestamp: 1500}, nil)
// Mock DataFrameBuilder calls
fakeDf := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 100, Timestamp: 1000},
{Offset: 150, Timestamp: 1500},
},
TraceSet: types.TraceSet{
"trace1": []float32{1.0, 1.5},
},
}
dfBuilder.On("NewFromKeysAndRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(1500, 0), mock.Anything).Return(fakeDf, nil)
api.traceValuesHandler(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
var resp TraceValuesResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Results, 1)
require.Len(t, resp.Results["trace1"], 2)
require.Equal(t, int64(150), resp.Results["trace1"][1].CommitNumber)
}
func TestTraceValuesHandler_WithLegacyAnomalies(t *testing.T) {
config.Config = &config.InstanceConfig{
FetchAnomaliesFromSql: false,
FetchChromePerfAnomalies: true,
}
w := httptest.NewRecorder()
req := TraceValuesRequest{
Ids: []string{"trace1"},
Begin: 1000,
End: 2000,
}
body, _ := json.Marshal(req)
r := httptest.NewRequest("POST", "/_/trace_values", bytes.NewReader(body))
dfBuilder := dataframeMocks.NewDataFrameBuilder(t)
perfGit := gitMocks.NewGit(t)
legacyAnomalyStore := &anomalyMocks.Store{}
api := NewTraceValuesApi(dfBuilder, perfGit, nil, legacyAnomalyStore)
fakeDf := &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 100, Timestamp: 1000},
{Offset: 200, Timestamp: 2000},
},
TraceSet: types.TraceSet{
"trace1": []float32{1.0, 2.0},
},
}
dfBuilder.On("NewFromKeysAndRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0), mock.Anything).Return(fakeDf, nil)
fakeAnomalyMap := chromeperf.AnomalyMap{
"trace1": {
types.CommitNumber(100): {Id: "legacy_anomaly1", State: "untriaged"},
},
}
legacyAnomalyStore.On("GetAnomaliesInTimeRange", mock.Anything, []string{"trace1"}, time.Unix(1000, 0), time.Unix(2000, 0)).Return(fakeAnomalyMap, nil)
api.traceValuesHandler(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
var resp TraceValuesResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.Results, 1)
require.NotNil(t, resp.AnomalyMap)
require.Equal(t, "legacy_anomaly1", resp.AnomalyMap["trace1"][100].Id)
}