blob: 0b56775fd420a2b1902d08f8d34a6cbf5e097c8c [file] [log] [blame]
package web
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/golden/go/blame"
"go.skia.org/infra/golden/go/clstore"
mock_clstore "go.skia.org/infra/golden/go/clstore/mocks"
"go.skia.org/infra/golden/go/code_review"
ci "go.skia.org/infra/golden/go/continuous_integration"
"go.skia.org/infra/golden/go/digest_counter"
"go.skia.org/infra/golden/go/expstorage"
"go.skia.org/infra/golden/go/ignore"
mock_ignore "go.skia.org/infra/golden/go/ignore/mocks"
"go.skia.org/infra/golden/go/indexer"
mock_indexer "go.skia.org/infra/golden/go/indexer/mocks"
"go.skia.org/infra/golden/go/mocks"
"go.skia.org/infra/golden/go/paramsets"
bug_revert "go.skia.org/infra/golden/go/testutils/data_bug_revert"
"go.skia.org/infra/golden/go/tjstore"
mock_tjstore "go.skia.org/infra/golden/go/tjstore/mocks"
"go.skia.org/infra/golden/go/types"
"go.skia.org/infra/golden/go/types/expectations"
"go.skia.org/infra/golden/go/web/frontend"
)
// TestByQuerySunnyDay is a unit test of the /byquery endpoint.
// It uses some example data based on the bug_revert corpus, which
// has some untriaged images that are easy to identify blames for.
func TestByQuerySunnyDay(t *testing.T) {
unittest.SmallTest(t)
mi := &mock_indexer.IndexSource{}
defer mi.AssertExpectations(t)
// We stop just before the "revert" in the fake data set, so it appears there are more untriaged
// digests going on.
fis := makeBugRevertIndex(3)
mi.On("GetIndex").Return(fis)
wh := Handlers{
HandlersConfig: HandlersConfig{
Indexer: mi,
},
}
output, err := wh.computeByBlame(context.Background(), "gm")
require.NoError(t, err)
commits := bug_revert.MakeTestCommits()
assert.Equal(t, []ByBlameEntry{
{
GroupID: bug_revert.SecondCommitHash,
NDigests: 2,
NTests: 2,
Commits: []*tiling.Commit{commits[1]},
AffectedTests: []TestRollup{
{
Test: bug_revert.TestOne,
Num: 1,
SampleDigest: bug_revert.UntriagedDigestBravo,
},
{
Test: bug_revert.TestTwo,
Num: 1,
SampleDigest: bug_revert.UntriagedDigestDelta,
},
},
},
{
GroupID: bug_revert.ThirdCommitHash,
NDigests: 1,
NTests: 1,
Commits: []*tiling.Commit{commits[2]},
AffectedTests: []TestRollup{
{
Test: bug_revert.TestTwo,
Num: 1,
SampleDigest: bug_revert.UntriagedDigestFoxtrot,
},
},
},
}, output)
}
// TestByQuerySunnyDaySimpler gets the ByBlame for a set of data with fewer untriaged outstanding.
func TestByQuerySunnyDaySimpler(t *testing.T) {
unittest.SmallTest(t)
mi := &mock_indexer.IndexSource{}
defer mi.AssertExpectations(t)
// Go all the way to the end, which has cleared up all untriaged digests except for
// UntriagedDigestFoxtrot
fis := makeBugRevertIndex(5)
mi.On("GetIndex").Return(fis)
commits := bug_revert.MakeTestCommits()
wh := Handlers{
HandlersConfig: HandlersConfig{
Indexer: mi,
},
}
output, err := wh.computeByBlame(context.Background(), "gm")
require.NoError(t, err)
assert.Equal(t, []ByBlameEntry{
{
GroupID: bug_revert.ThirdCommitHash,
NDigests: 1,
NTests: 1,
Commits: []*tiling.Commit{commits[2]},
AffectedTests: []TestRollup{
{
Test: bug_revert.TestTwo,
Num: 1,
SampleDigest: bug_revert.UntriagedDigestFoxtrot,
},
},
},
}, output)
}
// makeBugRevertIndex returns a search index corresponding to a subset of the bug_revert_data
// (which currently has nothing ignored). We choose to use this instead of mocking
// out the SearchIndex, as per the advice in http://go/mocks#prefer-real-objects
// of "prefer to use real objects if possible". We have tests that verify these
// real objects work correctly, so we should feel safe to use them here.
func makeBugRevertIndex(endIndex int) *indexer.SearchIndex {
tile := bug_revert.MakeTestTile()
// Trim is [start, end)
tile, err := tile.Trim(0, endIndex)
if err != nil {
panic(err) // this means our static data is horribly broken
}
cpxTile := types.NewComplexTile(tile)
dc := digest_counter.New(tile)
ps := paramsets.NewParamSummary(tile, dc)
exp := &mocks.ExpectationsStore{}
exp.On("Get", testutils.AnyContext).Return(bug_revert.MakeTestExpectations(), nil).Maybe()
b, err := blame.New(cpxTile.GetTile(types.ExcludeIgnoredTraces), bug_revert.MakeTestExpectations())
if err != nil {
panic(err) // this means our static data is horribly broken
}
si, err := indexer.SearchIndexForTesting(cpxTile, [2]digest_counter.DigestCounter{dc, dc}, [2]paramsets.ParamSummary{ps, ps}, exp, b)
if err != nil {
panic(err) // this means our static data is horribly broken
}
return si
}
// makeBugRevertIndex returns a search index corresponding to the bug_revert_data
// with the given ignores. Like makeBugRevertIndex, we return a real SearchIndex.
// If multiplier is > 1, duplicate traces will be added to the tile to make it artificially
// bigger.
func makeBugRevertIndexWithIgnores(ir []ignore.Rule, multiplier int) *indexer.SearchIndex {
tile := bug_revert.MakeTestTile()
add := make([]types.TracePair, 0, multiplier*len(tile.Traces))
for i := 1; i < multiplier; i++ {
for id, tr := range tile.Traces {
newID := tiling.TraceID(fmt.Sprintf("%s,copy=%d", id, i))
add = append(add, types.TracePair{ID: newID, Trace: tr.(*types.GoldenTrace)})
}
}
for _, tp := range add {
tile.Traces[tp.ID] = tp.Trace
}
cpxTile := types.NewComplexTile(tile)
subtile, combinedRules, err := ignore.FilterIgnored(tile, ir)
if err != nil {
panic(err) // this means our static data is horribly broken
}
cpxTile.SetIgnoreRules(subtile, combinedRules)
dcInclude := digest_counter.New(tile)
dcExclude := digest_counter.New(subtile)
psInclude := paramsets.NewParamSummary(tile, dcInclude)
psExclude := paramsets.NewParamSummary(subtile, dcExclude)
exp := &mocks.ExpectationsStore{}
exp.On("Get", testutils.AnyContext).Return(bug_revert.MakeTestExpectations(), nil).Maybe()
b, err := blame.New(cpxTile.GetTile(types.ExcludeIgnoredTraces), bug_revert.MakeTestExpectations())
if err != nil {
panic(err) // this means our static data is horribly broken
}
si, err := indexer.SearchIndexForTesting(cpxTile,
[2]digest_counter.DigestCounter{dcExclude, dcInclude},
[2]paramsets.ParamSummary{psExclude, psInclude}, exp, b)
if err != nil {
panic(err) // this means our static data is horribly broken
}
return si
}
// TestGetChangeListsSunnyDay tests the core functionality of
// listing all ChangeLists that have Gold results.
func TestGetChangeListsSunnyDay(t *testing.T) {
unittest.SmallTest(t)
mcls := &mock_clstore.Store{}
defer mcls.AssertExpectations(t)
mcls.On("GetChangeLists", testutils.AnyContext, clstore.SearchOptions{
StartIdx: 0,
Limit: 50,
}).Return(makeCodeReviewCLs(), 3, nil)
mcls.On("System").Return("gerrit")
wh := Handlers{
HandlersConfig: HandlersConfig{
CodeReviewURLTemplate: "example.com/cl/%s#templates",
ChangeListStore: mcls,
},
}
cls, pagination, err := wh.getIngestedChangeLists(context.Background(), 0, 50, false)
assert.NoError(t, err)
assert.Len(t, cls, 3)
assert.NotNil(t, pagination)
assert.Equal(t, &httputils.ResponsePagination{
Offset: 0,
Size: 50,
Total: 3,
}, pagination)
expected := makeWebCLs()
assert.Equal(t, expected, cls)
}
func TestGetActiveChangeListsSunnyDay(t *testing.T) {
unittest.SmallTest(t)
mcls := &mock_clstore.Store{}
defer mcls.AssertExpectations(t)
mcls.On("GetChangeLists", testutils.AnyContext, clstore.SearchOptions{
StartIdx: 20,
Limit: 30,
OpenCLsOnly: true,
}).Return(makeCodeReviewCLs(), 3, nil)
mcls.On("System").Return("gerrit")
wh := Handlers{
HandlersConfig: HandlersConfig{
CodeReviewURLTemplate: "example.com/cl/%s#templates",
ChangeListStore: mcls,
},
}
cls, pagination, err := wh.getIngestedChangeLists(context.Background(), 20, 30, true)
assert.NoError(t, err)
assert.Len(t, cls, 3)
assert.NotNil(t, pagination)
assert.Equal(t, &httputils.ResponsePagination{
Offset: 20,
Size: 30,
Total: 3,
}, pagination)
expected := makeWebCLs()
assert.Equal(t, expected, cls)
}
func makeCodeReviewCLs() []code_review.ChangeList {
return []code_review.ChangeList{
{
SystemID: "1002",
Owner: "other@example.com",
Status: code_review.Open,
Subject: "new feature",
Updated: time.Date(2019, time.August, 27, 0, 0, 0, 0, time.UTC),
},
{
SystemID: "1001",
Owner: "test@example.com",
Status: code_review.Landed,
Subject: "land gold",
Updated: time.Date(2019, time.August, 26, 0, 0, 0, 0, time.UTC),
},
{
SystemID: "1000",
Owner: "test@example.com",
Status: code_review.Abandoned,
Subject: "gold experiment",
Updated: time.Date(2019, time.August, 25, 0, 0, 0, 0, time.UTC),
},
}
}
func makeWebCLs() []frontend.ChangeList {
return []frontend.ChangeList{
{
System: "gerrit",
SystemID: "1002",
Owner: "other@example.com",
Status: "Open",
Subject: "new feature",
Updated: time.Date(2019, time.August, 27, 0, 0, 0, 0, time.UTC),
URL: "example.com/cl/1002#templates",
},
{
System: "gerrit",
SystemID: "1001",
Owner: "test@example.com",
Status: "Landed",
Subject: "land gold",
Updated: time.Date(2019, time.August, 26, 0, 0, 0, 0, time.UTC),
URL: "example.com/cl/1001#templates",
},
{
System: "gerrit",
SystemID: "1000",
Owner: "test@example.com",
Status: "Abandoned",
Subject: "gold experiment",
Updated: time.Date(2019, time.August, 25, 0, 0, 0, 0, time.UTC),
URL: "example.com/cl/1000#templates",
},
}
}
// TestGetCLSummarySunnyDay represents a case where we have a CL that
// has 2 patchsets with data, PS with order 1, ps with order 4
func TestGetCLSummarySunnyDay(t *testing.T) {
unittest.SmallTest(t)
expectedCLID := "1002"
mcls := &mock_clstore.Store{}
mtjs := &mock_tjstore.Store{}
defer mcls.AssertExpectations(t)
defer mtjs.AssertExpectations(t)
mcls.On("GetChangeList", testutils.AnyContext, expectedCLID).Return(makeCodeReviewCLs()[0], nil)
mcls.On("GetPatchSets", testutils.AnyContext, expectedCLID).Return(makeCodeReviewPSs(), nil)
mcls.On("System").Return("gerrit")
psID := tjstore.CombinedPSID{
CL: expectedCLID,
CRS: "gerrit",
PS: "ps-1",
}
tj1 := []ci.TryJob{
{
SystemID: "bb1",
DisplayName: "Test-Build",
Updated: time.Date(2019, time.August, 27, 1, 0, 0, 0, time.UTC),
},
}
mtjs.On("GetTryJobs", testutils.AnyContext, psID).Return(tj1, nil)
psID = tjstore.CombinedPSID{
CL: expectedCLID,
CRS: "gerrit",
PS: "ps-4",
}
tj2 := []ci.TryJob{
{
SystemID: "bb2",
DisplayName: "Test-Build",
Updated: time.Date(2019, time.August, 27, 0, 15, 0, 0, time.UTC),
},
{
SystemID: "bb3",
DisplayName: "Test-Code",
Updated: time.Date(2019, time.August, 27, 0, 20, 0, 0, time.UTC),
},
}
mtjs.On("GetTryJobs", testutils.AnyContext, psID).Return(tj2, nil)
mtjs.On("System").Return("buildbucket")
wh := Handlers{
HandlersConfig: HandlersConfig{
ContinuousIntegrationURLTemplate: "example.com/tj/%s#wow",
CodeReviewURLTemplate: "example.com/cl/%s#templates",
ChangeListStore: mcls,
TryJobStore: mtjs,
},
}
cl, err := wh.getCLSummary(context.Background(), expectedCLID)
assert.NoError(t, err)
assert.Equal(t, frontend.ChangeListSummary{
CL: makeWebCLs()[0], // matches expectedCLID
NumTotalPatchSets: 4,
PatchSets: []frontend.PatchSet{
{
SystemID: "ps-1",
Order: 1,
TryJobs: []frontend.TryJob{
{
System: "buildbucket",
SystemID: "bb1",
DisplayName: "Test-Build",
Updated: time.Date(2019, time.August, 27, 1, 0, 0, 0, time.UTC),
URL: "example.com/tj/bb1#wow",
},
},
},
{
SystemID: "ps-4",
Order: 4,
TryJobs: []frontend.TryJob{
{
System: "buildbucket",
SystemID: "bb2",
DisplayName: "Test-Build",
Updated: time.Date(2019, time.August, 27, 0, 15, 0, 0, time.UTC),
URL: "example.com/tj/bb2#wow",
},
{
System: "buildbucket",
SystemID: "bb3",
DisplayName: "Test-Code",
Updated: time.Date(2019, time.August, 27, 0, 20, 0, 0, time.UTC),
URL: "example.com/tj/bb3#wow",
},
},
},
},
}, cl)
}
func makeCodeReviewPSs() []code_review.PatchSet {
// This data is arbitrary
return []code_review.PatchSet{
{
SystemID: "ps-1",
ChangeListID: "1002",
Order: 1,
GitHash: "d6ac82ac4ee426b5ce2061f78cc02f9fe1db587e",
},
{
SystemID: "ps-4",
ChangeListID: "1002",
Order: 4,
GitHash: "45247158d641ece6318f2598fefecfce86a61ae0",
},
}
}
// TestTriageMaster tests a common case of a developer triaging a single test on the
// master branch.
func TestTriageMaster(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
defer mes.AssertExpectations(t)
user := "user@example.com"
mes.On("AddChange", testutils.AnyContext, []expstorage.Delta{
{
Grouping: bug_revert.TestOne,
Digest: bug_revert.UntriagedDigestBravo,
Label: expectations.Negative,
},
}, user).Return(nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
},
}
tr := frontend.TriageRequest{
ChangeListID: "",
TestDigestStatus: map[types.TestName]map[types.Digest]string{
bug_revert.TestOne: {
bug_revert.UntriagedDigestBravo: expectations.Negative.String(),
},
},
}
err := wh.triage(context.Background(), user, tr)
assert.NoError(t, err)
}
// TestTriageChangeList tests a common case of a developer triaging a single test on a ChangeList.
func TestTriageChangeList(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
clExp := &mocks.ExpectationsStore{}
mcs := &mock_clstore.Store{}
defer mes.AssertExpectations(t)
defer clExp.AssertExpectations(t)
defer mcs.AssertExpectations(t)
clID := "12345"
crs := "github"
user := "user@example.com"
mes.On("ForChangeList", clID, crs).Return(clExp)
clExp.On("AddChange", testutils.AnyContext, []expstorage.Delta{
{
Grouping: bug_revert.TestOne,
Digest: bug_revert.UntriagedDigestBravo,
Label: expectations.Negative,
},
}, user).Return(nil)
mcs.On("System").Return(crs)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
ChangeListStore: mcs,
},
}
tr := frontend.TriageRequest{
ChangeListID: clID,
TestDigestStatus: map[types.TestName]map[types.Digest]string{
bug_revert.TestOne: {
bug_revert.UntriagedDigestBravo: expectations.Negative.String(),
},
},
}
err := wh.triage(context.Background(), user, tr)
assert.NoError(t, err)
}
// TestBulkTriageMaster tests the case of a developer triaging multiple tests at once
// (via bulk triage).
func TestBulkTriageMaster(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
defer mes.AssertExpectations(t)
user := "user@example.com"
matcher := mock.MatchedBy(func(delta []expstorage.Delta) bool {
assert.Contains(t, delta, expstorage.Delta{
Grouping: bug_revert.TestOne,
Digest: bug_revert.GoodDigestAlfa,
Label: expectations.Untriaged,
})
assert.Contains(t, delta, expstorage.Delta{
Grouping: bug_revert.TestOne,
Digest: bug_revert.UntriagedDigestBravo,
Label: expectations.Negative,
})
assert.Contains(t, delta, expstorage.Delta{
Grouping: bug_revert.TestTwo,
Digest: bug_revert.GoodDigestCharlie,
Label: expectations.Positive,
})
assert.Contains(t, delta, expstorage.Delta{
Grouping: bug_revert.TestTwo,
Digest: bug_revert.UntriagedDigestDelta,
Label: expectations.Negative,
})
return true
})
mes.On("AddChange", testutils.AnyContext, matcher, user).Return(nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
},
}
tr := frontend.TriageRequest{
ChangeListID: "",
TestDigestStatus: map[types.TestName]map[types.Digest]string{
bug_revert.TestOne: {
bug_revert.GoodDigestAlfa: expectations.Untriaged.String(),
bug_revert.UntriagedDigestBravo: expectations.Negative.String(),
},
bug_revert.TestTwo: {
bug_revert.GoodDigestCharlie: expectations.Positive.String(),
bug_revert.UntriagedDigestDelta: expectations.Negative.String(),
},
},
}
err := wh.triage(context.Background(), user, tr)
assert.NoError(t, err)
}
// TestTriageMasterLegacy tests a common case of a developer triaging a single test using the
// legacy code (which has "0" as key issue instead of empty string.
func TestTriageMasterLegacy(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
defer mes.AssertExpectations(t)
user := "user@example.com"
mes.On("AddChange", testutils.AnyContext, []expstorage.Delta{
{
Grouping: bug_revert.TestOne,
Digest: bug_revert.UntriagedDigestBravo,
Label: expectations.Negative,
},
}, user).Return(nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
},
}
tr := frontend.TriageRequest{
ChangeListID: "0",
TestDigestStatus: map[types.TestName]map[types.Digest]string{
bug_revert.TestOne: {
bug_revert.UntriagedDigestBravo: expectations.Negative.String(),
},
},
}
err := wh.triage(context.Background(), user, tr)
assert.NoError(t, err)
}
// TestNew makes sure that if we omit values from HandlersConfig, New returns an error, depending
// on which validation mode is set.
func TestNewChecksValues(t *testing.T) {
unittest.SmallTest(t)
hc := HandlersConfig{}
_, err := NewHandlers(hc, BaselineSubset)
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot be nil")
hc = HandlersConfig{
GCSClient: &mocks.GCSClient{},
Baseliner: &mocks.BaselineFetcher{},
ChangeListStore: &mock_clstore.Store{},
}
_, err = NewHandlers(hc, BaselineSubset)
assert.NoError(t, err)
_, err = NewHandlers(hc, FullFrontEnd)
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot be nil")
}
// TestGetTriageLogSunnyDay tests getting the triage log and converting them to the appropriate
// types.
func TestGetTriageLogSunnyDay(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
defer mes.AssertExpectations(t)
masterBranch := ""
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
},
}
ts1 := time.Date(2019, time.October, 5, 4, 3, 2, 0, time.UTC)
ts2 := time.Date(2019, time.October, 6, 7, 8, 9, 0, time.UTC)
const offset = 10
const size = 20
mes.On("QueryLog", testutils.AnyContext, offset, size, false).Return([]expstorage.TriageLogEntry{
{
ID: "abc",
ChangeCount: 1,
User: "user1@example.com",
TS: ts1,
Details: []expstorage.Delta{
{
Label: expectations.Positive,
Digest: bug_revert.UntriagedDigestDelta,
Grouping: bug_revert.TestOne,
},
},
},
{
ID: "abc",
ChangeCount: 2,
User: "user1@example.com",
TS: ts2,
Details: []expstorage.Delta{
{
Label: expectations.Positive,
Digest: bug_revert.UntriagedDigestBravo,
Grouping: bug_revert.TestOne,
},
{
Label: expectations.Negative,
Digest: bug_revert.GoodDigestCharlie,
Grouping: bug_revert.TestOne,
},
},
},
}, offset+2, nil)
tle, n, err := wh.getTriageLog(context.Background(), masterBranch, offset, size, false)
assert.NoError(t, err)
assert.Equal(t, offset+2, n)
assert.Len(t, tle, 2)
assert.Equal(t, []frontend.TriageLogEntry{
{
ID: "abc",
ChangeCount: 1,
User: "user1@example.com",
TS: ts1.Unix() * 1000,
Details: []frontend.TriageDelta{
{
Label: expectations.Positive.String(),
Digest: bug_revert.UntriagedDigestDelta,
TestName: bug_revert.TestOne,
},
},
},
{
ID: "abc",
ChangeCount: 2,
User: "user1@example.com",
TS: ts2.Unix() * 1000,
Details: []frontend.TriageDelta{
{
Label: expectations.Positive.String(),
Digest: bug_revert.UntriagedDigestBravo,
TestName: bug_revert.TestOne,
},
{
Label: expectations.Negative.String(),
Digest: bug_revert.GoodDigestCharlie,
TestName: bug_revert.TestOne,
},
},
},
}, tle)
}
// TestDigestListHandlerSunnyDay tests the usual case of fetching digests for a given test.
func TestDigestListHandlerSunnyDay(t *testing.T) {
unittest.SmallTest(t)
mi := &mock_indexer.IndexSource{}
defer mi.AssertExpectations(t)
// We stop just before the "revert" in the fake data set, so it appears there are more untriaged
// digests going on.
fis := makeBugRevertIndex(3)
mi.On("GetIndex").Return(fis)
wh := Handlers{
HandlersConfig: HandlersConfig{
Indexer: mi,
},
}
dlr := wh.getDigestsResponse(string(bug_revert.TestOne), "todo")
assert.Equal(t, frontend.DigestListResponse{
Digests: []types.Digest{bug_revert.GoodDigestAlfa, bug_revert.UntriagedDigestBravo},
}, dlr)
}
func TestListIgnoresNoCounts(t *testing.T) {
unittest.SmallTest(t)
mis := &mock_ignore.Store{}
defer mis.AssertExpectations(t)
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
IgnoreStore: mis,
},
}
xir, err := wh.getIgnores(context.Background(), false)
require.NoError(t, err)
clearParsedQueries(xir)
assert.Equal(t, []*frontend.IgnoreRule{
{
ID: "1234",
Name: "user@example.com",
UpdatedBy: "user2@example.com",
Expires: firstRuleExpire,
Query: "device=delta",
Note: "Flaky driver",
},
{
ID: "5678",
Name: "user2@example.com",
UpdatedBy: "user@example.com",
Expires: secondRuleExpire,
Query: "name=test_two&source_type=gm",
Note: "Not ready yet",
},
{
ID: "-1",
Name: "user3@example.com",
UpdatedBy: "user3@example.com",
Expires: thirdRuleExpire,
Query: "matches=nothing",
Note: "Oops, this matches nothing",
},
}, xir)
}
func TestListIgnoresCountsSunnyDay(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
mi := &mock_indexer.IndexSource{}
mis := &mock_ignore.Store{}
defer mes.AssertExpectations(t)
defer mi.AssertExpectations(t)
defer mis.AssertExpectations(t)
exp := bug_revert.MakeTestExpectations()
// This makes the data a bit more interesting
exp.Set(bug_revert.TestTwo, bug_revert.GoodDigestEcho, expectations.Untriaged)
mes.On("Get", testutils.AnyContext).Return(exp, nil)
fis := makeBugRevertIndexWithIgnores(makeIgnoreRules(), 1)
mi.On("GetIndex").Return(fis)
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
IgnoreStore: mis,
Indexer: mi,
},
}
xir, err := wh.getIgnores(context.Background(), true)
require.NoError(t, err)
clearParsedQueries(xir)
assert.Equal(t, []*frontend.IgnoreRule{
{
ID: "1234",
Name: "user@example.com",
UpdatedBy: "user2@example.com",
Expires: firstRuleExpire,
Query: "device=delta",
Note: "Flaky driver",
Count: 2,
ExclusiveCount: 1,
UntriagedCount: 1,
ExclusiveUntriagedCount: 0,
},
{
ID: "5678",
Name: "user2@example.com",
UpdatedBy: "user@example.com",
Expires: secondRuleExpire,
Query: "name=test_two&source_type=gm",
Note: "Not ready yet",
Count: 4,
ExclusiveCount: 3,
UntriagedCount: 2,
ExclusiveUntriagedCount: 1,
},
{
ID: "-1",
Name: "user3@example.com",
UpdatedBy: "user3@example.com",
Expires: thirdRuleExpire,
Query: "matches=nothing",
Note: "Oops, this matches nothing",
Count: 0,
ExclusiveCount: 0,
UntriagedCount: 0,
ExclusiveUntriagedCount: 0,
},
}, xir)
}
// TestListIgnoresCountsBigTile uses an artificially bigger tile to process to make sure
// the counting code has no races in it when sharded up.
func TestListIgnoresCountsBigTile(t *testing.T) {
unittest.SmallTest(t)
mes := &mocks.ExpectationsStore{}
mi := &mock_indexer.IndexSource{}
mis := &mock_ignore.Store{}
defer mes.AssertExpectations(t)
defer mi.AssertExpectations(t)
defer mis.AssertExpectations(t)
exp := bug_revert.MakeTestExpectations()
// This makes the data a bit more interesting
exp.Set(bug_revert.TestTwo, bug_revert.GoodDigestEcho, expectations.Untriaged)
mes.On("Get", testutils.AnyContext).Return(exp, nil)
fis := makeBugRevertIndexWithIgnores(makeIgnoreRules(), 50)
mi.On("GetIndex").Return(fis)
mis.On("List", testutils.AnyContext).Return(makeIgnoreRules(), nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
ExpectationsStore: mes,
IgnoreStore: mis,
Indexer: mi,
},
}
xir, err := wh.getIgnores(context.Background(), true)
require.NoError(t, err)
// Just check the length, other unit tests will validate the correctness.
assert.Len(t, xir, 3)
}
func TestAddIgnoreRule(t *testing.T) {
unittest.SmallTest(t)
const user = "test@example.com"
const filter = "a=b&c=d"
const note = "skbug:9744"
mis := &mock_ignore.Store{}
defer mis.AssertExpectations(t)
expectedRule := ignore.Rule{
ID: "",
Name: user,
UpdatedBy: user,
Expires: firstRuleExpire,
Query: filter,
Note: note,
}
mis.On("Create", testutils.AnyContext, expectedRule).Return(nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
IgnoreStore: mis,
},
}
err := wh.addIgnoreRule(context.Background(), user, firstRuleExpire, frontend.IgnoreRuleBody{
Duration: "not used", // this have already been processed to compute the expire time.
Filter: filter,
Note: note,
})
require.NoError(t, err)
}
func TestUpdateIgnoreRule(t *testing.T) {
unittest.SmallTest(t)
const id = "12345"
const user = "test@example.com"
const filter = "a=b&c=d"
const note = "skbug:9744"
mis := &mock_ignore.Store{}
defer mis.AssertExpectations(t)
expectedRule := ignore.Rule{
ID: id,
Name: user,
UpdatedBy: user,
Expires: firstRuleExpire,
Query: filter,
Note: note,
}
mis.On("Update", testutils.AnyContext, expectedRule).Return(nil)
wh := Handlers{
HandlersConfig: HandlersConfig{
IgnoreStore: mis,
},
}
err := wh.updateIgnoreRule(context.Background(), id, user, firstRuleExpire, frontend.IgnoreRuleBody{
Duration: "not used", // this have already been processed to compute the expire time.
Filter: filter,
Note: note,
})
require.NoError(t, err)
}
var (
// These dates are arbitrary and don't matter. The logic for determining if an alert has
// "expired" is handled on the frontend.
firstRuleExpire = time.Date(2019, time.November, 30, 3, 4, 5, 0, time.UTC)
secondRuleExpire = time.Date(2020, time.November, 30, 3, 4, 5, 0, time.UTC)
thirdRuleExpire = time.Date(2020, time.November, 27, 3, 4, 5, 0, time.UTC)
)
func makeIgnoreRules() []ignore.Rule {
return []ignore.Rule{
{
ID: "1234",
Name: "user@example.com",
UpdatedBy: "user2@example.com",
Expires: firstRuleExpire,
Query: "device=delta",
Note: "Flaky driver",
},
{
ID: "5678",
Name: "user2@example.com",
UpdatedBy: "user@example.com",
Expires: secondRuleExpire,
Query: "name=test_two&source_type=gm",
Note: "Not ready yet",
},
{
ID: "-1",
Name: "user3@example.com",
UpdatedBy: "user3@example.com",
Expires: thirdRuleExpire,
Query: "matches=nothing",
Note: "Oops, this matches nothing",
},
}
}
// clearParsedQueries removes the implementation detail parts of the IgnoreRule that don't make
// sense to assert against.
func clearParsedQueries(xir []*frontend.IgnoreRule) {
for _, ir := range xir {
ir.ParsedQuery = nil
}
}