blob: 56f37cdfd1259a16e2161feef9700d7479191e0f [file] [log] [blame]
package bt_tracestore
import (
"context"
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/stretchr/testify/mock"
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/go/bt"
"go.skia.org/infra/go/deepequal"
"go.skia.org/infra/go/fileutil"
"go.skia.org/infra/go/gcs/gcs_testutils"
"go.skia.org/infra/go/sktest"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/go/util"
"go.skia.org/infra/go/vcsinfo"
mock_vcs "go.skia.org/infra/go/vcsinfo/mocks"
"go.skia.org/infra/golden/go/serialize"
data "go.skia.org/infra/golden/go/testutils/data_three_devices"
"go.skia.org/infra/golden/go/tracestore"
"go.skia.org/infra/golden/go/types"
)
// TestBTTraceStorePutGet adds a bunch of entries one at a time and
// then retrieves the full tile.
func TestBTTraceStorePutGet(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
commits := data.MakeTestCommits()
mvcs := MockVCSWithCommits(commits, 0)
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// With no data, we should get an empty tile
actualTile, _, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.NotNil(t, actualTile)
assert.Empty(t, actualTile.Traces)
// This time is an arbitrary point in time
now := time.Date(2019, time.May, 5, 1, 3, 4, 0, time.UTC)
// Build a tile up from the individual data points, one at a time
traces := data.MakeTestTile().Traces
for _, trace := range traces {
gTrace, ok := trace.(*types.GoldenTrace)
assert.True(t, ok)
// Put them in backwards, just to test that order doesn't matter
for i := len(gTrace.Digests) - 1; i >= 0; i-- {
e := tracestore.Entry{
Digest: gTrace.Digests[i],
Params: gTrace.Keys,
}
err := traceStore.Put(ctx, commits[i].Hash, []*tracestore.Entry{&e}, now)
assert.NoError(t, err)
// roll forward the clock by an arbitrary amount of time
now = now.Add(7 * time.Second)
}
}
// Get the tile back and make sure it exactly matches the tile
// we hand-crafted for the test data.
actualTile, actualCommits, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.Equal(t, data.MakeTestTile(), actualTile)
assert.Equal(t, commits, actualCommits)
}
// TestBTTraceStorePutGetSpanTile is like TestBTTraceStorePutGet except the 3 commits
// are lined up to go across two tiles.
func TestBTTraceStorePutGetSpanTile(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
commits := data.MakeTestCommits()
mvcs := MockVCSWithCommits(commits, DefaultTileSize-2)
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test_span",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// With no data, we should get an empty tile
actualTile, _, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.NotNil(t, actualTile)
assert.Empty(t, actualTile.Traces)
// This time is an arbitrary point in time
now := time.Date(2019, time.May, 5, 1, 3, 4, 0, time.UTC)
// Build a tile up from the individual data points, one at a time
traces := data.MakeTestTile().Traces
for _, trace := range traces {
gTrace, ok := trace.(*types.GoldenTrace)
assert.True(t, ok)
// Put them in backwards, just to test that order doesn't matter
for i := len(gTrace.Digests) - 1; i >= 0; i-- {
e := tracestore.Entry{
Digest: gTrace.Digests[i],
Params: gTrace.Keys,
}
err := traceStore.Put(ctx, commits[i].Hash, []*tracestore.Entry{&e}, now)
assert.NoError(t, err)
// roll forward the clock by an arbitrary amount of time
now = now.Add(7 * time.Second)
}
}
// Get the tile back and make sure it exactly matches the tile
// we hand-crafted for the test data.
actualTile, actualCommits, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.Equal(t, data.MakeTestTile(), actualTile)
assert.Equal(t, commits, actualCommits)
}
// TestBTTraceStorePutGetGrouped adds a bunch of entries batched by device and
// then retrieves the full Tile.
func TestBTTraceStorePutGetGrouped(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
commits := data.MakeTestCommits()
mvcs := MockVCSWithCommits(commits, 0)
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test_grouped",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// With no data, we should get an empty tile
actualTile, _, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.NotNil(t, actualTile)
assert.Empty(t, actualTile.Traces)
// Build a tile up from the individual data points, one at a time
now := time.Date(2019, time.May, 5, 1, 3, 4, 0, time.UTC)
// Group the traces by device, so we should have 3 groups of 2 traces.
traces := data.MakeTestTile().Traces
byDevice := map[string][]*types.GoldenTrace{
data.AnglerDevice: nil,
data.BullheadDevice: nil,
data.CrosshatchDevice: nil,
}
for _, trace := range traces {
gTrace, ok := trace.(*types.GoldenTrace)
assert.True(t, ok)
assert.Len(t, gTrace.Digests, len(commits), "test data should have one digest per commit")
dev := gTrace.Keys["device"]
byDevice[dev] = append(byDevice[dev], gTrace)
}
assert.Len(t, byDevice, 3, "test data should have exactly 3 devices")
// for each trace, report a group of two digests for each commit.
for dev, gTraces := range byDevice {
assert.Len(t, gTraces, 2, "test data for %s should have exactly 2 traces", dev)
for i := 0; i < len(commits); i++ {
var entries []*tracestore.Entry
for _, gTrace := range gTraces {
entries = append(entries, &tracestore.Entry{
Digest: gTrace.Digests[i],
Params: gTrace.Keys,
})
}
err = traceStore.Put(ctx, commits[i].Hash, entries, now)
assert.NoError(t, err)
// roll forward the clock by an arbitrary amount of time
now = now.Add(3 * time.Minute)
}
}
// Get the tile back and make sure it exactly matches the tile
// we hand-crafted for the test data.
actualTile, actualCommits, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.Equal(t, data.MakeTestTile(), actualTile)
assert.Equal(t, commits, actualCommits)
}
// TestBTTraceStorePutGetThreaded is like TestBTTraceStorePutGet, just
// with a bunch of reads/writes done in simultaneous go routines in
// an effort to catch any race conditions.
func TestBTTraceStorePutGetThreaded(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
commits := data.MakeTestCommits()
mvcs := MockVCSWithCommits(commits, 0)
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test_threaded",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
wg := sync.WaitGroup{}
wg.Add(2)
now := time.Date(2019, time.May, 5, 1, 3, 4, 0, time.UTC)
readTile := func() {
defer wg.Done()
_, _, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
}
go readTile()
// Build a tile up from the individual data points, one at a time
traces := data.MakeTestTile().Traces
for _, trace := range traces {
gTrace, ok := trace.(*types.GoldenTrace)
assert.True(t, ok)
// Put them in backwards, just to test that order doesn't matter
for i := len(gTrace.Digests) - 1; i >= 0; i-- {
wg.Add(1)
go func(now time.Time, i int) {
defer wg.Done()
e := tracestore.Entry{
Digest: gTrace.Digests[i],
Params: gTrace.Keys,
}
err := traceStore.Put(ctx, commits[i].Hash, []*tracestore.Entry{&e}, now)
assert.NoError(t, err)
}(now, i)
now = now.Add(7 * time.Second)
}
}
go readTile()
wg.Wait()
// Get the tile back and make sure it exactly matches the tile
// we hand-crafted for the test data.
actualTile, actualCommits, err := traceStore.GetTile(ctx, len(commits))
assert.NoError(t, err)
assert.Equal(t, data.MakeTestTile(), actualTile)
assert.Equal(t, commits, actualCommits)
}
// TestBTTraceStoreGetDenseTile makes sure we get an empty tile
func TestBTTraceStoreGetDenseTileEmpty(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
commits := data.MakeTestCommits()
realCommitIndices := []int{300, 501, 557}
totalCommits := 1101
mvcs, _ := MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test_dense_empty",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// With no data, we should get an empty tile
actualTile, actualCommits, err := traceStore.GetDenseTile(ctx, len(commits))
assert.NoError(t, err)
assert.NotNil(t, actualTile)
assert.Empty(t, actualCommits)
assert.Empty(t, actualTile.Traces)
}
// TestBTTraceStoreGetDenseTile puts in a few data points sparsely spaced throughout
// time and makes sure we can call GetDenseTile to get them condensed together
// (i.e. with all the empty commits tossed out). It puts them in a variety of conditions
// to try to identify any edge cases.
func TestBTTraceStoreGetDenseTile(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
// 3 commits, arbitrarily spaced out across the last tile
commits := data.MakeTestCommits()
realCommitIndices := []int{795, 987, 1001}
totalCommits := (256 * 4) - 1
mvcs, lCommits := MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile := data.MakeTestTile()
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
// 3 commits, arbitrarily spaced out across 3 tiles, with no data
// in the most recent tile
commits = data.MakeTestCommits()
realCommitIndices = []int{300, 501, 557}
totalCommits = 1101
mvcs, lCommits = MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile = data.MakeTestTile()
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
// As above, just 2 commits
commits = data.MakeTestCommits()[1:]
realCommitIndices = []int{501, 557}
totalCommits = 1101
mvcs, lCommits = MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile = data.MakeTestTile()
expectedTile, err := expectedTile.Trim(1, 3)
assert.NoError(t, err)
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
// All commits are on the first commit of their tile
commits = data.MakeTestCommits()
realCommitIndices = []int{0, 256, 512}
totalCommits = 1101
mvcs, lCommits = MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile = data.MakeTestTile()
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
// All commits are on the last commit of their tile
commits = data.MakeTestCommits()
realCommitIndices = []int{255, 511, 767}
totalCommits = 1101
mvcs, lCommits = MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile = data.MakeTestTile()
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
// Empty tiles between commits
commits = data.MakeTestCommits()
realCommitIndices = []int{50, 800, 1100}
totalCommits = 1101
mvcs, lCommits = MockSparseVCSWithCommits(commits, realCommitIndices, totalCommits)
expectedTile = data.MakeTestTile()
testDenseTile(t, expectedTile, mvcs, commits, lCommits, realCommitIndices)
}
// testDenseTile takes the data from tile, Puts it into BT, then pulls the tile given
// the commit layout in VCS and returns it.
func testDenseTile(t *testing.T, tile *tiling.Tile, mvcs *mock_vcs.VCS, commits []*tiling.Commit, lCommits []*vcsinfo.LongCommit, realCommitIndices []int) {
defer mvcs.AssertExpectations(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "three_devices_test_dense",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// This time is an arbitrary point in time
now := time.Date(2019, time.May, 5, 1, 3, 4, 0, time.UTC)
// Build a tile up from the individual data points, one at a time
traces := tile.Traces
for _, trace := range traces {
gTrace, ok := trace.(*types.GoldenTrace)
assert.True(t, ok)
// Put them in backwards, just to test that order doesn't matter
for i := len(gTrace.Digests) - 1; i >= 0; i-- {
e := tracestore.Entry{
Digest: gTrace.Digests[i],
Params: gTrace.Keys,
}
err := traceStore.Put(ctx, commits[i].Hash, []*tracestore.Entry{&e}, now)
assert.NoError(t, err)
// roll forward the clock by an arbitrary amount of time
now = now.Add(7 * time.Second)
}
}
// Get the tile back and make sure it exactly matches the tile
// we hand-crafted for the test data.
actualTile, allCommits, err := traceStore.GetDenseTile(ctx, len(commits))
assert.NoError(t, err)
assert.Len(t, allCommits, len(lCommits)-realCommitIndices[0])
// In MockSparseVCSWithCommits, we change the time of the commits, so we need
// to update the expected times to match.
for i, c := range commits {
c.CommitTime = lCommits[realCommitIndices[i]].Timestamp.Unix()
}
tile.Commits = commits
assert.Equal(t, tile, actualTile)
}
// TestBTDigestMap tests the internal workings of storing the
// DigestMap. See BIGTABLE.md for more about the schemas for
// the DigestMap family and the id counter family.
func TestBTDigestMap(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "digest_map_test",
VCS: nil,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
dm, err := traceStore.getDigestMap(ctx)
assert.NoError(t, err)
assert.NotNil(t, dm)
// should be empty, except for the initial mapping.
assert.Equal(t, 1, dm.Len())
i, err := dm.ID(types.MISSING_DIGEST)
assert.NoError(t, err)
assert.Equal(t, missingDigestID, i)
digests := makeTestDigests(0, 100)
// add 90 of the 100 digests using update
ninety := make(types.DigestSet, 90)
for _, d := range digests[0:90] {
ninety[d] = true
}
dm, err = traceStore.updateDigestMap(ctx, ninety)
assert.NoError(t, err)
assert.NotNil(t, dm)
assert.Equal(t, 91, dm.Len())
// We can't check to see if our known digests map to
// a specific id because the digest map could present
// the digests in a non-deterministic order.
// We can spot check one of the ids though
_, err = dm.Digest(88)
assert.NoError(t, err)
ids, err := traceStore.getIDs(ctx, 3)
// The next 3 numbers should be 91, 92, 93 because they are
// monotonically increasing
assert.NoError(t, err)
assert.Equal(t, []digestID{91, 92, 93}, ids)
func() {
traceStore.availIDsMutex.Lock()
defer traceStore.availIDsMutex.Unlock()
assert.NotContains(t, traceStore.availIDs, digestID(92))
assert.NotContains(t, traceStore.availIDs, digestID(93))
assert.Contains(t, traceStore.availIDs, digestID(94))
}()
// give two ids back (pretend we used id 92)
traceStore.returnIDs([]digestID{91, 93})
func() {
traceStore.availIDsMutex.Lock()
defer traceStore.availIDsMutex.Unlock()
assert.NotContains(t, traceStore.availIDs, digestID(92))
assert.Contains(t, traceStore.availIDs, digestID(93))
assert.Contains(t, traceStore.availIDs, digestID(94))
}()
// call update with an overlap of new and old
twenty := make(types.DigestSet, 90)
for _, d := range digests[80:] {
twenty[d] = true
}
dm, err = traceStore.updateDigestMap(ctx, twenty)
assert.NoError(t, err)
assert.NotNil(t, dm)
assert.Equal(t, 101, dm.Len())
// Get it again and make sure it matches the last update phase.
dm2, err := traceStore.getDigestMap(ctx)
assert.NoError(t, err)
assert.NotNil(t, dm2)
assert.Equal(t, dm, dm2)
// Add a lot more digests to make sure the bulk requesting works
for i := 1; i < 10; i++ {
// 113 is an arbitrary prime number that does not divide batchIdRequest.
ds := make(types.DigestSet, 113)
ds.AddLists(makeTestDigests(113*i, 113))
dm, err := traceStore.updateDigestMap(ctx, ds)
assert.NoError(t, err)
assert.NotNil(t, dm)
}
}
// makeTestDigests returns n valid digests. These digests are easy
// for humans to understand, as they are just the hex values [0, 99]
// reversed and 0-padded to 32 chars long (a valid md5 hash).
func makeTestDigests(start, n int) []types.Digest {
xd := make([]types.Digest, n)
for i := 0; i < n; i++ {
// Reverse them to exercise the prefixing of the digestMap.
s := util.ReverseString(fmt.Sprintf("%032x", start+i))
xd[i] = types.Digest(s)
}
return xd
}
// TestGetTileKey tests the internal workings of deriving a
// tileKey from the commit index. See BIGTABLE.md for more.
func TestGetTileKey(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
btConf := BTConfig{
// Leaving other things blank because we won't actually hit BT or use VCS.
}
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
type testStruct struct {
InputRepoIndex int
ExpectedKey tileKey
ExpectedIndex int
}
// test data is valid, but arbitrary.
tests := []testStruct{
{
InputRepoIndex: 0,
ExpectedKey: tileKey(2147483647),
ExpectedIndex: 0,
},
{
InputRepoIndex: 10,
ExpectedKey: tileKey(2147483647),
ExpectedIndex: 10,
},
{
InputRepoIndex: 300,
ExpectedKey: tileKey(2147483646),
ExpectedIndex: 44,
},
{
InputRepoIndex: 123456,
ExpectedKey: tileKey(2147483165),
ExpectedIndex: 64,
},
}
for _, test := range tests {
key, index := traceStore.getTileKey(test.InputRepoIndex)
assert.Equal(t, test.ExpectedKey, key)
assert.Equal(t, test.ExpectedIndex, index)
}
}
// TestCalcShardedRowName tests the internal workings of sharding
// a given subkey.
func TestCalcShardedRowName(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
btConf := BTConfig{
// Leaving other things blank because we won't actually hit BT
// or use the VCS.
}
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
type testStruct struct {
InputKey tileKey
InputRowType string
InputSubKey string
ExpectedRowName string
}
// test data is valid, but arbitrary.
tests := []testStruct{
{
InputKey: tileKey(2147483647),
InputRowType: typeTrace,
InputSubKey: ",0=1,1=3,3=0,",
ExpectedRowName: "09:ts:t:2147483647:,0=1,1=3,3=0,",
},
{
InputKey: tileKey(2147483647),
InputRowType: typeTrace,
InputSubKey: ",0=1,1=3,9=0,",
ExpectedRowName: "13:ts:t:2147483647:,0=1,1=3,9=0,",
},
{
InputKey: tileKey(2147483540),
InputRowType: typeDigestMap,
InputSubKey: "abc",
ExpectedRowName: "02:ts:d:2147483540:abc",
},
{
InputKey: tileKey(2147483540),
InputRowType: typeDigestMap,
InputSubKey: "bcd",
ExpectedRowName: "25:ts:d:2147483540:bcd",
},
}
for _, test := range tests {
row := traceStore.calcShardedRowName(test.InputKey, test.InputRowType, test.InputSubKey)
assert.Equal(t, test.ExpectedRowName, row)
}
}
// TestRowAndColNameFromDigest tests the internal workings of sharding
// a digest for use in the digest map
func TestRowAndColNameFromDigest(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
btConf := BTConfig{
// Leaving other things blank because we won't actually hit BT
// or use the VCS.
}
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
type testStruct struct {
InputDigest types.Digest
ExpectedRowName string
ExpectedColName string
}
// test data is valid, but arbitrary.
tests := []testStruct{
{
InputDigest: types.Digest("9e1d402515193304cafbdf02b8fd751b"),
ExpectedRowName: "21:ts:d:0000000000:9e1",
ExpectedColName: "d402515193304cafbdf02b8fd751b",
},
{
InputDigest: types.Digest("e30a49351a7e45b9591a989073e755b2"),
ExpectedRowName: "05:ts:d:0000000000:e30",
ExpectedColName: "a49351a7e45b9591a989073e755b2",
},
{
InputDigest: types.Digest("60b5e978d116cccc0ef12278d724245b"),
ExpectedRowName: "23:ts:d:0000000000:60b",
ExpectedColName: "5e978d116cccc0ef12278d724245b",
},
}
for _, test := range tests {
row, col := traceStore.rowAndColNameFromDigest(test.InputDigest)
assert.Equal(t, test.ExpectedRowName, row)
assert.Equal(t, test.ExpectedColName, col)
}
}
const (
// Directory with testdata.
TEST_DATA_DIR = "./testdata"
// Local file location of the test data.
TEST_DATA_PATH = TEST_DATA_DIR + "/10-test-sample-4bytes.tile"
// Folder in the testdata bucket. See go/testutils for details.
TEST_DATA_STORAGE_PATH = "gold-testdata/10-test-sample-4bytes.tile"
TILE_LENGTH = 50
)
// TestBTTraceStoreLargeTile stores a large amount of data into the tracestore
// and retrieves it.
func TestBTTraceStoreLargeTile(t *testing.T) {
unittest.LargeTest(t)
unittest.RequiresBigTableEmulator(t)
btConf, mvcs, tile := setupLargeTile(t)
defer mvcs.AssertExpectations(t)
ctx := context.Background()
traceStore, err := New(ctx, btConf, true)
assert.NoError(t, err)
// For each value in tile get the traceIDs that are not empty.
traceIDsPerCommit := make([]tiling.TraceIdSlice, TILE_LENGTH)
for traceID, trace := range tile.Traces {
gTrace := trace.(*types.GoldenTrace)
for i := 0; i < TILE_LENGTH; i++ {
if gTrace.Digests[i] != types.MISSING_DIGEST {
traceIDsPerCommit[i] = append(traceIDsPerCommit[i], traceID)
}
}
}
indices := make([]int, TILE_LENGTH)
maxIndex := 0
maxLen := len(traceIDsPerCommit[0])
for idx := range indices {
if len(traceIDsPerCommit[idx]) > maxLen {
maxLen = len(traceIDsPerCommit[idx])
maxIndex = idx
}
indices[idx] = idx
}
// Ingest the biggest tile.
entries := []*tracestore.Entry{}
allDigests := map[types.Digest]bool{"": true}
for _, traceID := range traceIDsPerCommit[maxIndex] {
t := tile.Traces[traceID].(*types.GoldenTrace)
digest := t.Digests[maxIndex]
allDigests[digest] = true
entries = append(entries, &tracestore.Entry{Digest: digest, Params: t.Params()})
}
assert.NoError(t, traceStore.Put(ctx, tile.Commits[maxIndex].Hash, entries, time.Now()))
foundDigestMap, err := traceStore.getDigestMap(ctx)
assert.NoError(t, err)
assert.Equal(t, len(allDigests), foundDigestMap.Len())
for digest := range allDigests {
id, err := foundDigestMap.ID(digest)
assert.NoError(t, err)
if digest == "" {
assert.Equal(t, missingDigestID, id)
} else {
assert.NotEqual(t, missingDigestID, id)
}
}
traceIDsPerCommit[maxIndex] = []tiling.TraceId{}
// Randomly add samples from the tile to that
for len(indices) > 0 {
idx := indices[0]
indices = indices[1:]
if len(traceIDsPerCommit[idx]) == 0 {
continue
}
entries := []*tracestore.Entry{}
for _, traceID := range traceIDsPerCommit[idx] {
t := tile.Traces[traceID].(*types.GoldenTrace)
digest := t.Digests[idx]
allDigests[digest] = true
entries = append(entries, &tracestore.Entry{Digest: digest, Params: t.Params()})
}
assert.NoError(t, traceStore.Put(ctx, tile.Commits[idx].Hash, entries, time.Now()))
}
// Load the tile and verify it's identical.
foundTile, commits, err := traceStore.GetTile(ctx, TILE_LENGTH)
assert.NoError(t, err)
assert.NotNil(t, commits)
assert.Equal(t, tile.Commits[len(tile.Commits)-TILE_LENGTH:], commits)
assert.Equal(t, len(tile.Traces), len(foundTile.Traces))
for traceID, trace := range tile.Traces {
gt := trace.(*types.GoldenTrace)
params := gt.Params()
found := false
foundCount := 0
for _, foundTrace := range foundTile.Traces {
if deepequal.DeepEqual(params, foundTrace.Params()) {
foundCount++
}
}
assert.Equal(t, 1, foundCount)
for foundID, foundTrace := range foundTile.Traces {
if deepequal.DeepEqual(params, foundTrace.Params()) {
expDigests := gt.Digests[len(gt.Digests)-TILE_LENGTH:]
found = true
fgt := foundTrace.(*types.GoldenTrace)
assert.Equal(t, len(expDigests), len(fgt.Digests))
var diff []string
diffStr := ""
for idx, digest := range expDigests {
isDiff := digest != fgt.Digests[idx]
if isDiff {
diff = append(diff, fmt.Sprintf("%d", idx))
diffStr += fmt.Sprintf(" %q != %q \n", digest, fgt.Digests[idx])
}
}
// Nothing should be different
assert.Nil(t, diff)
assert.Equal(t, "", diffStr)
delete(foundTile.Traces, foundID)
break
}
}
assert.True(t, found)
delete(tile.Traces, traceID)
}
assert.Equal(t, 0, len(foundTile.Traces))
assert.Equal(t, 0, len(tile.Traces))
}
func setupLargeTile(t sktest.TestingT) (BTConfig, *mock_vcs.VCS, *tiling.Tile) {
if !fileutil.FileExists(TEST_DATA_PATH) {
err := gcs_testutils.DownloadTestDataFile(t, gcs_testutils.TEST_DATA_BUCKET, TEST_DATA_STORAGE_PATH, TEST_DATA_PATH)
assert.NoError(t, err, "Unable to download testdata.")
}
tile := makeSampleTile(t, TEST_DATA_PATH)
assert.Len(t, tile.Commits, TILE_LENGTH)
mvcs := MockVCSWithCommits(tile.Commits, 0)
btConf := BTConfig{
ProjectID: "should-use-the-emulator",
InstanceID: "testinstance",
TableID: "large_tile_test",
VCS: mvcs,
}
assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
assert.NoError(t, InitBT(btConf))
fmt.Println("BT emulator set up")
return btConf, mvcs, tile
}
func makeSampleTile(t sktest.TestingT, fileName string) *tiling.Tile {
file, err := os.Open(fileName)
assert.NoError(t, err)
sample, err := serialize.DeserializeSample(file)
assert.NoError(t, err)
return sample.Tile
}
func MockVCSWithCommits(commits []*tiling.Commit, offset int) *mock_vcs.VCS {
mvcs := &mock_vcs.VCS{}
indexCommits := make([]*vcsinfo.IndexCommit, 0, len(commits))
hashes := make([]string, 0, len(commits))
longCommits := make([]*vcsinfo.LongCommit, 0, len(commits))
for i, c := range commits {
mvcs.On("IndexOf", ctx, c.Hash).Return(i+offset, nil).Maybe()
indexCommits = append(indexCommits, &vcsinfo.IndexCommit{
Hash: c.Hash,
Index: i + offset,
Timestamp: time.Unix(c.CommitTime, 0),
})
hashes = append(hashes, c.Hash)
longCommits = append(longCommits, &vcsinfo.LongCommit{
ShortCommit: &vcsinfo.ShortCommit{
Hash: c.Hash,
Author: c.Author,
Subject: fmt.Sprintf("Commit #%d in test", i),
},
Timestamp: time.Unix(c.CommitTime, 0),
})
}
mvcs.On("LastNIndex", len(commits)).Return(indexCommits)
mvcs.On("DetailsMulti", ctx, hashes, false).Return(longCommits, nil)
return mvcs
}
func MockSparseVCSWithCommits(commits []*tiling.Commit, realCommitIndices []int, totalCommits int) (*mock_vcs.VCS, []*vcsinfo.LongCommit) {
mvcs := &mock_vcs.VCS{}
if len(commits) != len(realCommitIndices) {
panic("commits should be same length as realCommitIndices")
}
// Create many synthetic commits.
indexCommits := make([]*vcsinfo.IndexCommit, totalCommits)
longCommits := make([]*vcsinfo.LongCommit, totalCommits)
hashes := []string{}
for i := 0; i < totalCommits; i++ {
h := fmt.Sprintf("%040d", i)
indexCommits[i] = &vcsinfo.IndexCommit{
Hash: h,
Index: i,
// space the commits 1700 seconds apart, starting at the epoch
// This is an arbitrary amount of space.
Timestamp: time.Unix(int64(i*1700), 0),
}
longCommits[i] = &vcsinfo.LongCommit{
ShortCommit: &vcsinfo.ShortCommit{
Hash: h,
Author: "nobody@example.com",
},
Timestamp: time.Unix(int64(i*1700), 0),
}
hashes = append(hashes, h)
}
for i, c := range commits {
index := realCommitIndices[i]
mvcs.On("IndexOf", ctx, c.Hash).Return(index, nil).Maybe()
indexCommits[index] = &vcsinfo.IndexCommit{
Hash: c.Hash,
Index: index,
Timestamp: time.Unix(int64(index*1700), 0),
}
hashes[index] = c.Hash
longCommits[index] = &vcsinfo.LongCommit{
ShortCommit: &vcsinfo.ShortCommit{
Hash: c.Hash,
Author: c.Author,
Subject: fmt.Sprintf("Real commit #%d in test", i),
},
Timestamp: time.Unix(int64(index*1700), 0),
}
}
firstRealCommitIdx := realCommitIndices[0]
mvcs.On("ByIndex", ctx, firstRealCommitIdx).Return(longCommits[firstRealCommitIdx], nil).Maybe()
mvcs.On("From", mock.Anything).Return(hashes[firstRealCommitIdx:], nil).Maybe()
mvcs.On("LastNIndex", 1).Return(indexCommits[totalCommits-1:]).Maybe()
mvcs.On("DetailsMulti", ctx, hashes[firstRealCommitIdx:], false).Return(longCommits[firstRealCommitIdx:], nil).Maybe()
return mvcs, longCommits
}
var ctx = mock.AnythingOfType("*context.emptyCtx")