blob: 99a5cc2c72c82a76d690cf77ee85ff25c36a9908 [file] [log] [blame]
package fs_expstore
import (
"context"
"fmt"
"strconv"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/deepequal"
"go.skia.org/infra/go/eventbus/mocks"
"go.skia.org/infra/go/firestore"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/golden/go/expstorage"
data "go.skia.org/infra/golden/go/testutils/data_three_devices"
"go.skia.org/infra/golden/go/types"
"go.skia.org/infra/golden/go/types/expectations"
)
// TestGetExpectations writes some changes and then reads back the
// aggregated results.
func TestGetExpectations(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
// Brand new instance should have no expectations
e, err := f.Get()
require.NoError(t, err)
require.True(t, e.Empty())
err = f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaUntriaged1Digest,
Label: expectations.Positive,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}, userOne)
require.NoError(t, err)
err = f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaUntriaged1Digest, // overwrites previous
Label: expectations.Untriaged,
},
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, userTwo)
require.NoError(t, err)
e, err = f.Get()
require.NoError(t, err)
assertExpectationsMatchDefaults(t, e)
// Make sure that if we create a new view, we can read the results immediately.
fr, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
e, err = fr.Get()
require.NoError(t, err)
assertExpectationsMatchDefaults(t, e)
}
func assertExpectationsMatchDefaults(t *testing.T, e expectations.ReadOnly) {
assert.Equal(t, expectations.Positive, e.Classification(data.AlphaTest, data.AlphaGood1Digest))
assert.Equal(t, expectations.Negative, e.Classification(data.AlphaTest, data.AlphaBad1Digest))
assert.Equal(t, expectations.Untriaged, e.Classification(data.AlphaTest, data.AlphaUntriaged1Digest))
assert.Equal(t, expectations.Positive, e.Classification(data.BetaTest, data.BetaGood1Digest))
assert.Equal(t, expectations.Untriaged, e.Classification(data.BetaTest, data.BetaUntriaged1Digest))
assert.Equal(t, 3, e.Len())
}
// TestGetExpectationsSnapShot has both a read-write and a read version and makes sure
// that the changes to the read-write version eventually propagate to the read version
// via the QuerySnapshot.
func TestGetExpectationsSnapShot(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
err = f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaUntriaged1Digest,
Label: expectations.Positive,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}, userOne)
require.NoError(t, err)
ro, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
require.NotNil(t, ro)
exp, err := ro.Get()
require.NoError(t, err)
require.Equal(t, expectations.Positive, exp.Classification(data.AlphaTest, data.AlphaUntriaged1Digest))
require.Equal(t, expectations.Positive, exp.Classification(data.AlphaTest, data.AlphaGood1Digest))
require.Equal(t, expectations.Untriaged, exp.Classification(data.AlphaTest, data.AlphaBad1Digest))
require.Equal(t, 2, exp.Len())
err = f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaUntriaged1Digest, // overwrites previous
Label: expectations.Untriaged,
},
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, userTwo)
require.NoError(t, err)
e, err := ro.Get()
require.NoError(t, err)
assertExpectationsMatchDefaults(t, e)
}
// TestGetExpectationsRace writes a bunch of data from many go routines
// in an effort to catch any race conditions in the caching layer.
func TestGetExpectationsRace(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
type entry struct {
Grouping types.TestName
Digest types.Digest
Label expectations.Label
}
entries := []entry{
{
Grouping: data.AlphaTest,
Digest: data.AlphaUntriaged1Digest,
Label: expectations.Untriaged,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
{
Grouping: data.BetaTest,
Digest: data.BetaUntriaged1Digest,
Label: expectations.Untriaged,
},
}
wg := sync.WaitGroup{}
for i := 0; i < 50; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
e := entries[i%len(entries)]
err := f.AddChange(ctx, []expstorage.Delta{
{
Grouping: e.Grouping,
Digest: e.Digest,
Label: e.Label,
},
}, userOne)
require.NoError(t, err)
}(i)
// Make sure we can read and write w/o races
if i%5 == 0 {
_, err := f.Get()
require.NoError(t, err)
}
}
wg.Wait()
e, err := f.Get()
require.NoError(t, err)
assertExpectationsMatchDefaults(t, e)
}
// TestGetExpectationsBig writes 32^2=1024 entries
// to test the batch writing.
func TestGetExpectationsBig(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
// Write the expectations in two, non-overlapping blocks.
exp1, delta1 := makeBigExpectations(0, 16)
exp2, delta2 := makeBigExpectations(16, 32)
expected := exp1.DeepCopy()
expected.MergeExpectations(exp2)
wg := sync.WaitGroup{}
// Write them concurrently to test for races.
wg.Add(2)
go func() {
defer wg.Done()
err := f.AddChange(ctx, delta1, userOne)
require.NoError(t, err)
}()
go func() {
defer wg.Done()
err := f.AddChange(ctx, delta2, userTwo)
require.NoError(t, err)
}()
wg.Wait()
// We wait for the query snapshots to be notified about the change.
require.Eventually(t, func() bool {
// Fetch a copy to avoid a race between Get() and DeepEqual
e, err := f.GetCopy()
assert.NoError(t, err)
return deepequal.DeepEqual(expected, e)
}, 10*time.Second, 100*time.Millisecond)
// Make sure that if we create a new view, we can read the results
// from the table to make the expectations
fr, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
e, err := fr.GetCopy()
require.NoError(t, err)
require.Equal(t, expected, e)
}
// TestReadOnly ensures a read-only instance fails to write data.
func TestReadOnly(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
err = f.AddChange(context.Background(), []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}, userOne)
require.Error(t, err)
require.Contains(t, err.Error(), "read-only")
}
// TestQueryLog tests that we can query logs at a given place
func TestQueryLog(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
fillWith4Entries(t, f)
entries, n, err := f.QueryLog(ctx, 0, 100, false)
require.NoError(t, err)
require.Equal(t, 4, n) // 4 operations
require.Len(t, entries, 4)
now := time.Now()
normalizeEntries(t, now, entries)
require.Equal(t, []expstorage.TriageLogEntry{
{
ID: "was_random_0",
User: userTwo,
TS: now,
ChangeCount: 2,
Details: nil,
},
{
ID: "was_random_1",
User: userOne,
TS: now,
ChangeCount: 1,
Details: nil,
},
{
ID: "was_random_2",
User: userTwo,
TS: now,
ChangeCount: 1,
Details: nil,
},
{
ID: "was_random_3",
User: userOne,
TS: now,
ChangeCount: 1,
Details: nil,
},
}, entries)
entries, n, err = f.QueryLog(ctx, 1, 2, false)
require.NoError(t, err)
require.Equal(t, expstorage.CountMany, n)
require.Len(t, entries, 2)
normalizeEntries(t, now, entries)
require.Equal(t, []expstorage.TriageLogEntry{
{
ID: "was_random_0",
User: userOne,
TS: now,
ChangeCount: 1,
Details: nil,
},
{
ID: "was_random_1",
User: userTwo,
TS: now,
ChangeCount: 1,
Details: nil,
},
}, entries)
// Make sure we can handle an invalid offset
entries, n, err = f.QueryLog(ctx, 500, 100, false)
require.NoError(t, err)
require.Equal(t, 500, n) // The system guesses that there are 500 or fewer items.
require.Empty(t, entries)
}
// TestQueryLogDetails checks that the details are filled in when requested.
func TestQueryLogDetails(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
fillWith4Entries(t, f)
entries, n, err := f.QueryLog(ctx, 0, 100, true)
require.NoError(t, err)
require.Equal(t, 4, n) // 4 operations
require.Len(t, entries, 4)
// These should be sorted, starting with the most recent
require.Equal(t, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.BetaTest,
Digest: data.BetaUntriaged1Digest,
Label: expectations.Untriaged,
},
}, entries[0].Details)
require.Equal(t, []expstorage.Delta{
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, entries[1].Details)
require.Equal(t, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}, entries[2].Details)
require.Equal(t, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Negative,
},
}, entries[3].Details)
}
// TestQueryLogDetailsLarge checks that the details are filled in correctly, even in cases
// where we had to write in multiple chunks. (skbug.com/9485)
func TestQueryLogDetailsLarge(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
// 800 should spread us across 3 "shards", which are ~250 expectations.
const numExp = 800
delta := make([]expstorage.Delta, 0, numExp)
for i := uint64(0); i < numExp; i++ {
n := types.TestName(fmt.Sprintf("test_%03d", i))
// An MD5 hash is 128 bits, which is 32 chars
d := types.Digest(fmt.Sprintf("%032d", i))
delta = append(delta, expstorage.Delta{
Grouping: n,
Digest: d,
Label: expectations.Positive,
})
}
err = f.AddChange(ctx, delta, "test@example.com")
require.NoError(t, err)
entries, n, err := f.QueryLog(ctx, 0, 2, true)
require.NoError(t, err)
require.Equal(t, 1, n) // 1 big operation
require.Len(t, entries, 1)
entry := entries[0]
require.Equal(t, numExp, entry.ChangeCount)
require.Len(t, entry.Details, numExp)
// spot check some details
require.Equal(t, expstorage.Delta{
Grouping: "test_000",
Digest: "00000000000000000000000000000000",
Label: expectations.Positive,
}, entry.Details[0])
require.Equal(t, expstorage.Delta{
Grouping: "test_200",
Digest: "00000000000000000000000000000200",
Label: expectations.Positive,
}, entry.Details[200])
require.Equal(t, expstorage.Delta{
Grouping: "test_400",
Digest: "00000000000000000000000000000400",
Label: expectations.Positive,
}, entry.Details[400])
require.Equal(t, expstorage.Delta{
Grouping: "test_600",
Digest: "00000000000000000000000000000600",
Label: expectations.Positive,
}, entry.Details[600])
require.Equal(t, expstorage.Delta{
Grouping: "test_799",
Digest: "00000000000000000000000000000799",
Label: expectations.Positive,
}, entry.Details[799])
}
// TestUndoChangeSunnyDay checks undoing entries that exist.
func TestUndoChangeSunnyDay(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
fillWith4Entries(t, f)
entries, n, err := f.QueryLog(ctx, 0, 4, false)
require.NoError(t, err)
require.Equal(t, expstorage.CountMany, n)
require.Len(t, entries, 4)
err = f.UndoChange(ctx, entries[0].ID, userOne)
require.NoError(t, err)
err = f.UndoChange(ctx, entries[2].ID, userOne)
require.NoError(t, err)
// Check that the undone items were applied
exp, err := f.Get()
require.NoError(t, err)
assertMatches := func(e expectations.ReadOnly) {
assert.Equal(t, e.Classification(data.AlphaTest, data.AlphaGood1Digest), expectations.Negative)
assert.Equal(t, e.Classification(data.AlphaTest, data.AlphaBad1Digest), expectations.Untriaged)
assert.Equal(t, e.Classification(data.BetaTest, data.BetaGood1Digest), expectations.Positive)
assert.Equal(t, e.Classification(data.BetaTest, data.BetaUntriaged1Digest), expectations.Untriaged)
assert.Equal(t, 2, e.Len())
}
assertMatches(exp)
// Make sure that if we create a new view, we can read the results
// from the table to make the expectations
fr, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
exp, err = fr.Get()
require.NoError(t, err)
assertMatches(exp)
}
// TestUndoChangeUntriaged checks undoing entries that were set to Untriaged. For example,
// a user accidentally marks something as untriaged and then undoes that.
func TestUndoChangeUntriaged(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
}, userOne))
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Untriaged,
},
}, userTwo))
// Make sure the "oops" marking of untriaged was applied:
exp, err := f.Get()
require.NoError(t, err)
require.Equal(t, expectations.Positive, exp.Classification(data.AlphaTest, data.AlphaGood1Digest))
require.Equal(t, expectations.Untriaged, exp.Classification(data.AlphaTest, data.AlphaBad1Digest))
require.Equal(t, 1, exp.Len())
entries, _, err := f.QueryLog(ctx, 0, 1, false)
require.NoError(t, err)
require.Len(t, entries, 1)
err = f.UndoChange(ctx, entries[0].ID, userTwo)
require.NoError(t, err)
// Check that we reset from Untriaged back to Negative.
exp, err = f.Get()
require.NoError(t, err)
assertMatches := func(e expectations.ReadOnly) {
assert.Equal(t, expectations.Positive, e.Classification(data.AlphaTest, data.AlphaGood1Digest))
assert.Equal(t, expectations.Negative, e.Classification(data.AlphaTest, data.AlphaBad1Digest))
assert.Equal(t, 2, e.Len())
}
assertMatches(exp)
// Make sure that if we create a new view, we can read the results
// from the table to make the expectations
fr, err := New(ctx, c, nil, ReadOnly)
require.NoError(t, err)
exp, err = fr.Get()
require.NoError(t, err)
assertMatches(exp)
}
// TestUndoChangeNoExist checks undoing an entry that does not exist.
func TestUndoChangeNoExist(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
err = f.UndoChange(ctx, "doesnotexist", "userTwo")
require.Error(t, err)
require.Contains(t, err.Error(), "not find change")
}
// TestEventBusAddMaster makes sure proper eventbus signals are sent
// when changes are made to the master branch.
func TestEventBusAddMaster(t *testing.T) {
unittest.LargeTest(t)
meb := &mocks.EventBus{}
defer meb.AssertExpectations(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, meb, ReadWrite)
require.NoError(t, err)
change1 := []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}
change2 := []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}
meb.On("Publish", expstorage.EV_EXPSTORAGE_CHANGED, &expstorage.EventExpectationChange{
ExpectationDelta: change1[0],
CRSAndCLID: "",
}, /*global=*/ true).Once()
// This was two entries, which are split up into two firestore records. Thus, we should
// see two events, one for each of them.
meb.On("Publish", expstorage.EV_EXPSTORAGE_CHANGED, &expstorage.EventExpectationChange{
ExpectationDelta: change2[0],
CRSAndCLID: "",
}, /*global=*/ true).Once()
meb.On("Publish", expstorage.EV_EXPSTORAGE_CHANGED, &expstorage.EventExpectationChange{
ExpectationDelta: change2[1],
CRSAndCLID: "",
}, /*global=*/ true).Once()
require.NoError(t, f.AddChange(ctx, change1, userOne))
require.NoError(t, f.AddChange(ctx, change2, userTwo))
}
// TestEventBusUndo tests that eventbus signals are properly sent during Undo.
func TestEventBusUndo(t *testing.T) {
unittest.LargeTest(t)
meb := &mocks.EventBus{}
defer meb.AssertExpectations(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
f, err := New(ctx, c, meb, ReadWrite)
require.NoError(t, err)
change := expstorage.Delta{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Negative,
}
expectedUndo := expstorage.Delta{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Untriaged,
}
meb.On("Publish", expstorage.EV_EXPSTORAGE_CHANGED, &expstorage.EventExpectationChange{
ExpectationDelta: change,
CRSAndCLID: "",
}, /*global=*/ true).Once()
meb.On("Publish", expstorage.EV_EXPSTORAGE_CHANGED, &expstorage.EventExpectationChange{
ExpectationDelta: expectedUndo,
CRSAndCLID: "",
}, /*global=*/ true).Once()
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{change}, userOne))
entries, _, err := f.QueryLog(ctx, 0, 1, false)
require.NoError(t, err)
require.Len(t, entries, 1)
err = f.UndoChange(ctx, entries[0].ID, userOne)
require.NoError(t, err)
}
// TestCLExpectationsAddGet tests the separation of the MasterExpectations
// and the CLExpectations. It starts with a shared history, then
// adds some expectations to both, before requiring that they are properly dealt
// with. Specifically, the CLExpectations should be treated as a delta to
// the MasterExpectations (but doesn't actually contain MasterExpectations).
func TestCLExpectationsAddGet(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
mb, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
require.NoError(t, mb.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Negative,
},
}, userTwo))
ib := mb.ForChangeList("117", "gerrit") // arbitrary cl id
// Check that it starts out blank.
clExp, err := ib.Get()
require.NoError(t, err)
require.True(t, clExp.Empty())
// Add to the CLExpectations
require.NoError(t, ib.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, userOne))
// Add to the MasterExpectations
require.NoError(t, mb.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
}, userOne))
masterE, err := mb.Get()
require.NoError(t, err)
clExp, err = ib.Get()
require.NoError(t, err)
// Make sure the CLExpectations did not leak to the MasterExpectations
assert.Equal(t, masterE.Classification(data.AlphaTest, data.AlphaGood1Digest), expectations.Negative)
assert.Equal(t, masterE.Classification(data.AlphaTest, data.AlphaBad1Digest), expectations.Negative)
assert.Equal(t, masterE.Classification(data.BetaTest, data.BetaGood1Digest), expectations.Untriaged)
assert.Equal(t, 2, masterE.Len())
// Make sure the CLExpectations are separate from the MasterExpectations.
assert.Equal(t, clExp.Classification(data.AlphaTest, data.AlphaGood1Digest), expectations.Positive)
assert.Equal(t, clExp.Classification(data.AlphaTest, data.AlphaBad1Digest), expectations.Untriaged)
assert.Equal(t, clExp.Classification(data.BetaTest, data.BetaGood1Digest), expectations.Positive)
assert.Equal(t, 2, clExp.Len())
}
// TestCLExpectationsQueryLog makes sure the QueryLogs interacts
// with the CLExpectations as expected. Which is to say, the two
// logs are separate.
func TestCLExpectationsQueryLog(t *testing.T) {
unittest.LargeTest(t)
c, cleanup := firestore.NewClientForTesting(t)
defer cleanup()
ctx := context.Background()
mb, err := New(ctx, c, nil, ReadWrite)
require.NoError(t, err)
require.NoError(t, mb.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
}, userTwo))
ib := mb.ForChangeList("117", "gerrit") // arbitrary cl id
require.NoError(t, ib.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, userOne))
// Make sure the master logs are separate from the cl logs.
// request up to 10 to make sure we would get the cl
// change (if the filtering was wrong).
entries, n, err := mb.QueryLog(ctx, 0, 10, true)
require.NoError(t, err)
require.Equal(t, 1, n)
now := time.Now()
normalizeEntries(t, now, entries)
require.Equal(t, expstorage.TriageLogEntry{
ID: "was_random_0",
User: userTwo,
TS: now,
ChangeCount: 1,
Details: []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive,
},
},
}, entries[0])
// Make sure the cl logs are separate from the master logs.
// Unlike when getting the expectations, the cl logs are
// *only* those logs that affected this cl. Not, for example,
// all the master logs with the cl logs tacked on.
entries, n, err = ib.QueryLog(ctx, 0, 10, true)
require.NoError(t, err)
require.Equal(t, 1, n) // only one change on this branch
normalizeEntries(t, now, entries)
require.Equal(t, expstorage.TriageLogEntry{
ID: "was_random_0",
User: userOne,
TS: now,
ChangeCount: 1,
Details: []expstorage.Delta{
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
},
}, entries[0])
}
// TestExpectationEntryID tests edge cases for malformed names
func TestExpectationEntryID(t *testing.T) {
unittest.SmallTest(t)
// Based on real data
e := expectationEntry{
Grouping: "downsample/images/mandrill_512.png",
Digest: "36bc7da524f2869c97f0a0f1d7042110",
}
require.Equal(t, "downsample-images-mandrill_512.png|36bc7da524f2869c97f0a0f1d7042110",
e.ID())
}
// fillWith4Entries fills a given Store with 4 triaged records of a few digests.
func fillWith4Entries(t *testing.T, f *Store) {
ctx := context.Background()
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Negative,
},
}, userOne))
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaGood1Digest,
Label: expectations.Positive, // overwrites previous value
},
}, userTwo))
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.BetaTest,
Digest: data.BetaGood1Digest,
Label: expectations.Positive,
},
}, userOne))
require.NoError(t, f.AddChange(ctx, []expstorage.Delta{
{
Grouping: data.AlphaTest,
Digest: data.AlphaBad1Digest,
Label: expectations.Negative,
},
{
Grouping: data.BetaTest,
Digest: data.BetaUntriaged1Digest,
Label: expectations.Untriaged,
},
}, userTwo))
}
// Some parts of the entries (timestamp and id) are non-deterministic
// Make sure they are valid, then replace them with deterministic values
// for an easier comparison.
func normalizeEntries(t *testing.T, now time.Time, entries []expstorage.TriageLogEntry) {
for i, te := range entries {
require.NotEqual(t, "", te.ID)
te.ID = "was_random_" + strconv.Itoa(i)
ts := te.TS
require.False(t, ts.IsZero())
require.True(t, now.After(ts))
te.TS = now
entries[i] = te
}
}
// makeBigExpectations makes n tests named from start to end that each have 32 digests.
func makeBigExpectations(start, end int) (*expectations.Expectations, []expstorage.Delta) {
var e expectations.Expectations
var delta []expstorage.Delta
for i := start; i < end; i++ {
for j := 0; j < 32; j++ {
tn := types.TestName(fmt.Sprintf("test-%03d", i))
d := types.Digest(fmt.Sprintf("digest-%03d", j))
e.Set(tn, d, expectations.Positive)
delta = append(delta, expstorage.Delta{
Grouping: tn,
Digest: d,
Label: expectations.Positive,
})
}
}
return &e, delta
}
const (
userOne = "userOne@example.com"
userTwo = "userTwo@example.com"
)