blob: 7f3b431dffb1df925e29fca383ee1d24e1c5e8e7 [file] [log] [blame]
package ref_differ
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/golden/go/diff"
mock_diffstore "go.skia.org/infra/golden/go/diffstore/mocks"
"go.skia.org/infra/golden/go/digest_counter"
"go.skia.org/infra/golden/go/expectations"
mock_index "go.skia.org/infra/golden/go/indexer/mocks"
"go.skia.org/infra/golden/go/search/common"
"go.skia.org/infra/golden/go/search/frontend"
"go.skia.org/infra/golden/go/search/query"
"go.skia.org/infra/golden/go/types"
)
// TestGetRefDiffsSunnyDay tests getting the refs
// for an untriaged diff in a test that has two
// previously marked positive digests and one such negative digest.
func TestGetRefDiffsSunnyDay(t *testing.T) {
unittest.SmallTest(t)
es := makeExpClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]map[types.Digest]paramtools.ParamSet{
testName: {
alphaPositiveDigest: makeAlphaParamSet(),
betaNegativeDigest: makeBetaParamSet(),
gammaPositiveDigest: makeGammaParamSet(),
untriagedDigest: makeUntriagedParamSet(),
},
},
)
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]digest_counter.DigestCount{
testName: {
alphaPositiveDigest: 117,
betaNegativeDigest: 8,
gammaPositiveDigest: 93,
untriagedDigest: 7,
},
},
)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{alphaPositiveDigest, gammaPositiveDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
alphaPositiveDigest: makeDiffMetric(8),
gammaPositiveDigest: makeDiffMetric(2),
}, nil)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{betaNegativeDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
betaNegativeDigest: makeDiffMetric(9),
}, nil)
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
matches := []string{types.PrimaryKeyField} // This is the default for several gold queries.
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
err := rd.FillRefDiffs(context.Background(), &input, metric, matches, matchAll, types.ExcludeIgnoredTraces)
require.NoError(t, err)
assert.Equal(t, common.PositiveRef, input.ClosestRef)
pos := frontend.SRDiffDigest{
Digest: gammaPositiveDigest,
Status: "positive",
ParamSet: makeGammaParamSet(),
OccurrencesInTile: 93, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&pos, makeDiffMetric(2))
neg := frontend.SRDiffDigest{
Digest: betaNegativeDigest,
Status: "negative",
ParamSet: makeBetaParamSet(),
OccurrencesInTile: 8, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&neg, makeDiffMetric(9))
assert.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: &pos,
common.NegativeRef: &neg,
}, input.RefDiffs)
}
// TestGetRefDiffsTryJobSunnyDay tests getting the refs
// for an untriaged diff in a tryjob test that has two
// previously marked positive digests and one such negative digest.
func TestGetRefDiffsTryJobSunnyDay(t *testing.T) {
unittest.SmallTest(t)
es := makeExpClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]map[types.Digest]paramtools.ParamSet{
testName: {
alphaPositiveDigest: makeAlphaParamSet(),
betaNegativeDigest: makeBetaParamSet(),
gammaPositiveDigest: makeGammaParamSet(),
// untriagedDigest isn't here to emulate a tryjob run
},
},
)
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]digest_counter.DigestCount{
testName: {
alphaPositiveDigest: 117,
betaNegativeDigest: 8,
gammaPositiveDigest: 93,
// untriagedDigest isn't here to emulate a tryjob run
},
},
)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{alphaPositiveDigest, gammaPositiveDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
alphaPositiveDigest: makeDiffMetric(8),
gammaPositiveDigest: makeDiffMetric(2),
}, nil)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{betaNegativeDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
betaNegativeDigest: makeDiffMetric(9),
}, nil)
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
matches := []string{types.PrimaryKeyField} // This is the default for several gold queries.
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
err := rd.FillRefDiffs(context.Background(), &input, metric, matches, matchAll, types.ExcludeIgnoredTraces)
require.NoError(t, err)
assert.Equal(t, common.PositiveRef, input.ClosestRef)
pos := frontend.SRDiffDigest{
Digest: gammaPositiveDigest,
Status: "positive",
ParamSet: makeGammaParamSet(),
OccurrencesInTile: 93, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&pos, makeDiffMetric(2))
neg := frontend.SRDiffDigest{
Digest: betaNegativeDigest,
Status: "negative",
ParamSet: makeBetaParamSet(),
OccurrencesInTile: 8, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&neg, makeDiffMetric(9))
assert.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: &pos,
common.NegativeRef: &neg,
}, input.RefDiffs)
}
// TestGetRefDiffsAllUntriaged tests the case when there are a few untriaged digests
// on master, including the one we are trying to find a diff for.
func TestGetRefDiffsAllUntriaged(t *testing.T) {
unittest.SmallTest(t)
// Empty expectations => everything is untriaged.
es := expectations.EmptyClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]map[types.Digest]paramtools.ParamSet{
testName: {
alphaPositiveDigest: makeAlphaParamSet(),
betaNegativeDigest: makeBetaParamSet(),
gammaPositiveDigest: makeGammaParamSet(),
untriagedDigest: makeUntriagedParamSet(),
},
},
)
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]digest_counter.DigestCount{
testName: {
alphaPositiveDigest: 117,
betaNegativeDigest: 8,
gammaPositiveDigest: 93,
untriagedDigest: 3,
},
},
)
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
matches := []string{types.PrimaryKeyField}
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
err := rd.FillRefDiffs(context.Background(), &input, metric, matches, matchAll, types.ExcludeIgnoredTraces)
require.NoError(t, err)
require.Equal(t, common.NoRef, input.ClosestRef)
require.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: nil,
common.NegativeRef: nil,
}, input.RefDiffs)
}
// TestGetRefDiffsNoPrevious tests the case when the first digest for a test
// is uploaded an there are no positive nor negative matches seen previously.
func TestGetRefDiffsNoPrevious(t *testing.T) {
unittest.SmallTest(t)
es := expectations.EmptyClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(map[types.TestName]map[types.Digest]paramtools.ParamSet{})
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(map[types.TestName]digest_counter.DigestCount{})
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
matches := []string{types.PrimaryKeyField}
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
err := rd.FillRefDiffs(context.Background(), &input, metric, matches, matchAll, types.ExcludeIgnoredTraces)
require.NoError(t, err)
require.Equal(t, common.NoRef, input.ClosestRef)
require.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: nil,
common.NegativeRef: nil,
}, input.RefDiffs)
}
// TestGetRefDiffsMatches tests that we can supply multiple keys to
// match against.
func TestGetRefDiffsMatches(t *testing.T) {
unittest.SmallTest(t)
es := makeExpClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]map[types.Digest]paramtools.ParamSet{
testName: {
alphaPositiveDigest: makeAlphaParamSet(),
betaNegativeDigest: makeBetaParamSet(),
gammaPositiveDigest: makeGammaParamSet(),
},
},
)
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]digest_counter.DigestCount{
testName: {
alphaPositiveDigest: 117,
betaNegativeDigest: 8,
gammaPositiveDigest: 93,
},
},
)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{gammaPositiveDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
gammaPositiveDigest: makeDiffMetric(2),
}, nil)
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
matches := []string{"arch", types.PrimaryKeyField} // Only Gamma has x86 in the "arch" values.
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
err := rd.FillRefDiffs(context.Background(), &input, metric, matches, matchAll, types.ExcludeIgnoredTraces)
require.NoError(t, err)
require.Equal(t, common.PositiveRef, input.ClosestRef)
pos := frontend.SRDiffDigest{
Digest: gammaPositiveDigest,
Status: "positive",
ParamSet: makeGammaParamSet(),
OccurrencesInTile: 93, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&pos, makeDiffMetric(2))
require.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: &pos,
common.NegativeRef: nil,
}, input.RefDiffs)
}
// TestGetRefDiffsMatchRHS tests that we can provide a RHS query to match against.
func TestGetRefDiffsMatchRHS(t *testing.T) {
unittest.SmallTest(t)
es := makeExpClassifier()
mis := &mock_index.IndexSearcher{}
mds := &mock_diffstore.DiffStore{}
defer mis.AssertExpectations(t)
defer mds.AssertExpectations(t)
mis.On("GetParamsetSummaryByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]map[types.Digest]paramtools.ParamSet{
testName: {
alphaPositiveDigest: makeAlphaParamSet(),
betaNegativeDigest: makeBetaParamSet(),
gammaPositiveDigest: makeGammaParamSet(),
},
"should-be-ignored": { // this is provided to make sure we only match in a given test.
"ignorabledigest": makeAlphaParamSet(),
},
},
)
mis.On("DigestCountsByTest", types.ExcludeIgnoredTraces).Return(
map[types.TestName]digest_counter.DigestCount{
testName: {
alphaPositiveDigest: 117,
betaNegativeDigest: 8,
gammaPositiveDigest: 93,
},
"should-be-ignored": {
"ignorabledigest": 9999,
},
},
)
mds.On("Get", testutils.AnyContext, untriagedDigest, types.DigestSlice{alphaPositiveDigest}).Return(
map[types.Digest]*diff.DiffMetrics{
alphaPositiveDigest: makeDiffMetric(2),
}, nil)
rd := NewFirestoreImpl(es, mds, mis)
metric := query.CombinedMetric
input := frontend.SearchResult{
ParamSet: makeUntriagedParamSet(),
Digest: untriagedDigest,
Test: testName,
}
rhsQuery := paramtools.ParamSet{
"arch": []string{"z80"},
}
err := rd.FillRefDiffs(context.Background(), &input, metric, nil, rhsQuery, types.ExcludeIgnoredTraces)
require.NoError(t, err)
require.Equal(t, common.PositiveRef, input.ClosestRef)
pos := frontend.SRDiffDigest{
Digest: alphaPositiveDigest,
Status: "positive",
ParamSet: makeAlphaParamSet(),
OccurrencesInTile: 117, // These are the arbitrary numbers from DigestCountsByTest
}
addDiffMetrics(&pos, makeDiffMetric(2))
require.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
common.PositiveRef: &pos,
common.NegativeRef: nil,
}, input.RefDiffs)
}
var matchAll = paramtools.ParamSet{}
// All this test data is valid, but arbitrary.
const (
alphaPositiveDigest = types.Digest("aaa884cd5ac3d6785c35cff8f26d2da5")
betaNegativeDigest = types.Digest("bbb8d94852dfde3f3bebcc000be60153")
gammaPositiveDigest = types.Digest("ccc84ad6f1a0c628d5f27180e497309e")
untriagedDigest = types.Digest("7bf4d4e913605c0781697df4004191c5")
testName = types.TestName("some_test")
)
// makeDiffMetric makes a DiffMetrics object with
// a combined diff metric of n. All other data is
// based off of n, but not technically accurate.
func makeDiffMetric(n int) *diff.DiffMetrics {
return &diff.DiffMetrics{
NumDiffPixels: n * 100,
PixelDiffPercent: float32(n) / 10.0,
MaxRGBADiffs: [4]int{3 * n, 2 * n, n, n},
DimDiffer: false,
CombinedMetric: float32(n),
}
}
func addDiffMetrics(s *frontend.SRDiffDigest, d *diff.DiffMetrics) {
s.CombinedMetric = d.CombinedMetric
s.PixelDiffPercent = d.PixelDiffPercent
s.MaxRGBADiffs = d.MaxRGBADiffs
s.DimDiffer = d.DimDiffer
s.NumDiffPixels = d.NumDiffPixels
s.QueryMetric = d.CombinedMetric
}
// makeAlphaParamSet returns the ParamSet for the alphaPositiveDigest
func makeAlphaParamSet() paramtools.ParamSet {
return paramtools.ParamSet{
"arch": []string{"z80"},
"name": []string{string(testName)},
"os": []string{"Texas Instruments"},
}
}
// makeBetaParamSet returns the ParamSet for the betaPositiveDigest
func makeBetaParamSet() paramtools.ParamSet {
return paramtools.ParamSet{
"arch": []string{"x64"},
"name": []string{string(testName)},
"os": []string{"Android"},
}
}
// makeGammaParamSet returns the ParamSet for the gammaPositiveDigest
func makeGammaParamSet() paramtools.ParamSet {
// This means that both the arm and x86 bot drew the same thing
// for the given test.
return paramtools.ParamSet{
"arch": []string{"arm", "x86"},
"name": []string{string(testName)},
"os": []string{"Android"},
}
}
// makeUntriagedParamSet returns the ParamSet for the untriagedDigest
func makeUntriagedParamSet() paramtools.ParamSet {
return paramtools.ParamSet{
"arch": []string{"x86"},
types.PrimaryKeyField: []string{string(testName)},
"os": []string{"iPhone 38 Maxx"},
}
}
// makeExpClassifier returns a Classifier which has two positive entries and one negative one.
func makeExpClassifier() expectations.Classifier {
var expOne expectations.Expectations
expOne.Set(testName, alphaPositiveDigest, expectations.Positive)
expOne.Set(testName, gammaPositiveDigest, expectations.Positive)
var expTwo expectations.Expectations
expTwo.Set(testName, betaNegativeDigest, expectations.Negative)
return expectations.Join(&expOne, &expTwo)
}