[gold] GetDigestDetails should support old digests

Change-Id: I1d6ffcbe9651957076c982555fe2d53b786366c8
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/251461
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
diff --git a/golden/go/search/search.go b/golden/go/search/search.go
index 8853f32..b22625b 100644
--- a/golden/go/search/search.go
+++ b/golden/go/search/search.go
@@ -163,16 +163,17 @@
 }
 
 // GetDigestDetails implements the SearchAPI interface.
+// TODO(stephana): Make the metric, match and ignores parameters for the comparison.
 func (s *SearchImpl) GetDigestDetails(ctx context.Context, test types.TestName, digest types.Digest) (*frontend.DigestDetails, error) {
 	defer metrics2.FuncTimer().Stop()
 	idx := s.indexSource.GetIndex()
 
 	// Make sure we have valid data, i.e. we know about that test/digest
 	dct := idx.DigestCountsByTest(types.IncludeIgnoredTraces)
-	if digests, ok := dct[test]; !ok {
+
+	digests, ok := dct[test]
+	if !ok {
 		return nil, skerr.Fmt("unknown test %s", test)
-	} else if _, ok := digests[digest]; !ok {
-		return nil, skerr.Fmt("unknown digest %s for test %s", digest, test)
 	}
 
 	tile := idx.Tile().GetTile(types.IncludeIgnoredTraces)
@@ -183,19 +184,20 @@
 	}
 
 	oneInter := newSrIntermediate(test, digest, "", nil, nil)
-	byTrace := idx.DigestCountsByTrace(types.IncludeIgnoredTraces)
-	for traceId, t := range tile.Traces {
-		gTrace := t.(*types.GoldenTrace)
-		if gTrace.TestName() != test {
-			continue
-		}
-		if _, ok := byTrace[traceId][digest]; ok {
-			oneInter.add(traceId, t, nil)
+	if _, ok := digests[digest]; ok {
+		// We know a digest is somewhere in at least one trace. Iterate through all of them
+		// to find which ones.
+		byTrace := idx.DigestCountsByTrace(types.IncludeIgnoredTraces)
+		for traceId, t := range tile.Traces {
+			gTrace := t.(*types.GoldenTrace)
+			if gTrace.TestName() != test {
+				continue
+			}
+			if _, ok := byTrace[traceId][digest]; ok {
+				oneInter.add(traceId, t, nil)
+			}
 		}
 	}
-
-	// TODO(stephana): Make the metric, match and ignores parameters for the comparison.
-
 	// If there are no traces or params then set them to nil to signal there are none.
 	hasTraces := len(oneInter.traces) > 0
 	if !hasTraces {
diff --git a/golden/go/search/search_test.go b/golden/go/search/search_test.go
index c95c48e..7899ed8 100644
--- a/golden/go/search/search_test.go
+++ b/golden/go/search/search_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -825,23 +826,86 @@
 	}, details)
 }
 
-func TestDigestDetailsThreeDevicesBadDigest(t *testing.T) {
+// TestDigestDetailsThreeDevicesOldDigest represents the scenario in which a user is requesting
+// data about a digest that just went off the tile.
+func TestDigestDetailsThreeDevicesOldDigest(t *testing.T) {
 	unittest.SmallTest(t)
 
-	const digestWeWantDetailsAbout = types.Digest("invalid digest")
-	const testWeWantDetailsAbout = data.AlphaTest
+	const digestWeWantDetailsAbout = types.Digest("digest-too-old")
+	const testWeWantDetailsAbout = data.BetaTest
 
+	mes := &mocks.ExpectationsStore{}
 	mi := &mock_index.IndexSource{}
+	mds := &mock_diffstore.DiffStore{}
+	defer mes.AssertExpectations(t)
 	defer mi.AssertExpectations(t)
+	defer mds.AssertExpectations(t)
 
 	fis := makeThreeDevicesIndex()
 	mi.On("GetIndex").Return(fis)
 
-	s := New(nil, nil, mi, nil, nil, everythingPublic)
+	mes.On("Get").Return(data.MakeTestExpectations(), nil)
+
+	mds.On("UnavailableDigests", testutils.AnyContext).Return(map[types.Digest]*diff.DigestFailure{}, nil)
+
+	mds.On("Get", testutils.AnyContext, digestWeWantDetailsAbout, types.DigestSlice{data.BetaGood1Digest}).
+		Return(map[types.Digest]*diff.DiffMetrics{
+			data.BetaGood1Digest: makeSmallDiffMetric(),
+		}, nil)
+
+	s := New(mds, mes, mi, nil, nil, everythingPublic)
+
+	d, err := s.GetDigestDetails(context.Background(), testWeWantDetailsAbout, digestWeWantDetailsAbout)
+	require.NoError(t, err)
+	// spot check is fine for this test because other tests do a more thorough check of the
+	// whole struct.
+	assert.Equal(t, digestWeWantDetailsAbout, d.Digest.Digest)
+	assert.Equal(t, testWeWantDetailsAbout, d.Digest.Test)
+	assert.Equal(t, map[common.RefClosest]*frontend.SRDiffDigest{
+		common.PositiveRef: {
+			DiffMetrics: makeSmallDiffMetric(),
+			Digest:      data.BetaGood1Digest,
+			Status:      "positive",
+			ParamSet: paramtools.ParamSet{
+				"device":                []string{data.AnglerDevice, data.BullheadDevice},
+				types.PRIMARY_KEY_FIELD: []string{string(data.BetaTest)},
+				types.CORPUS_FIELD:      []string{"gm"},
+			},
+			OccurrencesInTile: 6,
+		},
+		common.NegativeRef: nil,
+	}, d.Digest.RefDiffs)
+}
+
+// TestDigestDetailsThreeDevicesOldDigest represents the scenario in which a user is requesting
+// data about a digest that never existed.
+func TestDigestDetailsThreeDevicesBadDigest(t *testing.T) {
+	unittest.SmallTest(t)
+
+	const digestWeWantDetailsAbout = types.Digest("unknown-digest")
+	const testWeWantDetailsAbout = data.BetaTest
+
+	mes := &mocks.ExpectationsStore{}
+	mi := &mock_index.IndexSource{}
+	mds := &mock_diffstore.DiffStore{}
+	defer mes.AssertExpectations(t)
+	defer mi.AssertExpectations(t)
+	defer mds.AssertExpectations(t)
+
+	fis := makeThreeDevicesIndex()
+	mi.On("GetIndex").Return(fis)
+
+	mes.On("Get").Return(data.MakeTestExpectations(), nil)
+
+	mds.On("UnavailableDigests", testutils.AnyContext).Return(map[types.Digest]*diff.DigestFailure{}, nil)
+
+	mds.On("Get", testutils.AnyContext, digestWeWantDetailsAbout, types.DigestSlice{data.BetaGood1Digest}).Return(nil, errors.New("invalid digest"))
+
+	s := New(mds, mes, mi, nil, nil, everythingPublic)
 
 	_, err := s.GetDigestDetails(context.Background(), testWeWantDetailsAbout, digestWeWantDetailsAbout)
 	require.Error(t, err)
-	assert.Contains(t, err.Error(), "unknown")
+	assert.Contains(t, err.Error(), "invalid")
 }
 
 func TestDigestDetailsThreeDevicesBadTest(t *testing.T) {