blob: 75be24edd2af12d5d933f0c071e8b27cdfc682fc [file] [log] [blame]
package search
import (
"fmt"
"math"
"net/url"
"os"
"testing"
"time"
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/go/eventbus"
"go.skia.org/infra/go/gcs"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/expstorage"
"go.skia.org/infra/golden/go/indexer"
"go.skia.org/infra/golden/go/mocks"
"go.skia.org/infra/golden/go/serialize"
"go.skia.org/infra/golden/go/storage"
"go.skia.org/infra/golden/go/types"
)
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"
// REPO_URL is the url of the repo to check out.
REPO_URL = "https://skia.googlesource.com/skia"
// REPO_DIR contains the location of where to check out Skia for benchmarks.
REPO_DIR = "./skia"
// N_COMMITS is the number of commits used in benchmarks.
N_COMMITS = 50
// Database user used by benchmarks.
DB_USER = "readwrite"
)
func TestCompareTests(t *testing.T) {
testutils.MediumTest(t)
testutils.SkipIfShort(t)
const MAX_DIM = 9999999
var HEAD = true
storages, idx, tile, _ := getStoragesIndexTile(t, gcs.TEST_DATA_BUCKET, TEST_DATA_STORAGE_PATH, TEST_DATA_PATH)
testNameSet, _ := findTests(tile, HEAD)
ctQuery := &CTQuery{
RowQuery: &Query{
Pos: true,
Neg: true,
Unt: true,
Head: HEAD,
IncludeIgnores: false,
Limit: MAX_DIM,
},
ColumnQuery: &Query{
Pos: true,
Neg: true,
Unt: true,
Head: HEAD,
IncludeIgnores: false,
Limit: MAX_DIM,
},
Match: []string{types.PRIMARY_KEY_FIELD},
SortRows: SORT_FIELD_COUNT,
SortColumns: SORT_FIELD_DIFF,
RowsDir: SORT_DESC,
ColumnsDir: SORT_ASC,
Metric: diff.METRIC_COMBINED,
}
// Test individual tests.
for testName := range testNameSet {
// Make sure the query searches for the current testName. It is assumed
// that all tests in the tile have source_type == 'gm'.
q, err := url.ParseQuery(fmt.Sprintf("source_type=gm&name=%s", testName))
assert.NoError(t, err)
ctQuery.RowQuery.Query = q
ctQuery.ColumnQuery.Query = q
testCompTest(t, MAX_DIM, MAX_DIM, testNameSet, ctQuery, idx, storages, 1)
}
// test across all tests.
q, err := url.ParseQuery("source_type=gm")
assert.NoError(t, err)
ctQuery.RowQuery.Query = q
ctQuery.ColumnQuery.Query = q
ctQuery.RowQuery.Limit = 1000000
ctQuery.ColumnQuery.Limit = 1000000
testCompTest(t, ctQuery.ColumnQuery.Limit, ctQuery.ColumnQuery.Limit, testNameSet, ctQuery, idx, storages, len(testNameSet))
}
func testCompTest(t *testing.T, maxLimit, maxRowLimit int32, testNameSet map[string]util.StringSet, ctQuery *CTQuery, idx *indexer.SearchIndex, storages *storage.Storage, nTests int) {
ret, err := CompareTest(ctQuery, storages, idx)
assert.NoError(t, err)
// Make sure the rows are as expected.
assert.True(t, int32(len(ret.Grid.Rows)) <= maxLimit)
uniqueTests := util.StringSet{}
lastCount := math.MaxInt64
for idx, row := range ret.Grid.Rows {
digestSet, ok := testNameSet[row.TestName]
assert.True(t, ok)
uniqueTests[row.TestName] = true
expectedCellsPerRow := util.MinInt(len(digestSet)-1, int(maxRowLimit))
// Make sure the count is monotonically increasing.
assert.True(t, lastCount >= row.N)
lastCount = row.N
foundDigestSet := util.StringSet{}
for _, cell := range row.Values {
foundDigestSet[cell.Digest] = true
}
// Make sure there are not duplicate digest in a row.
assert.Equal(t, len(row.Values), len(foundDigestSet))
// Make sure the 'row' digest is not in the row, i.e. compared to itself.
assert.False(t, foundDigestSet[ret.Grid.Rows[idx].Digest])
// Make sure we get the expected number of digests in this row.
assert.Equal(t, expectedCellsPerRow, len(row.Values))
// Make sure the found digests are fully contained in the whole set.
assert.Equal(t, digestSet.Intersect(foundDigestSet), foundDigestSet)
// Make sure the values are monotonically increasing.
if len(row.Values) > 0 {
lastVal := row.Values[0].Diffs[ctQuery.Metric]
for _, val := range row.Values[1:] {
_, ok := val.Diffs[ctQuery.Metric]
assert.True(t, ok)
assert.True(t, lastVal <= val.Diffs[ctQuery.Metric])
lastVal = val.Diffs[ctQuery.Metric]
}
}
}
assert.Equal(t, nTests, len(uniqueTests))
if len(uniqueTests) == 1 {
assert.Equal(t, 1, len(uniqueTests))
expectedRowCount := util.MinInt(len(testNameSet[uniqueTests.Keys()[0]]), int(maxLimit))
assert.Equal(t, expectedRowCount, len(ret.Grid.Rows))
}
}
func getStoragesIndexTile(t *testing.T, bucket, storagePath, outputPath string) (*storage.Storage, *indexer.SearchIndex, *tiling.Tile, *indexer.Indexer) {
err := gcs.DownloadTestDataFile(t, bucket, storagePath, outputPath)
assert.NoError(t, err, "Unable to download testdata.")
defer testutils.RemoveAll(t, TEST_DATA_DIR)
sample := loadSample(t, TEST_DATA_PATH)
tileBuilder := mocks.NewMockTileBuilderFromTile(t, sample.Tile)
eventBus := eventbus.New()
expStore := expstorage.NewMemExpectationsStore(eventBus)
err = expStore.AddChange(sample.Expectations.Tests, "testuser")
assert.NoError(t, err)
storages := &storage.Storage{
ExpectationsStore: expStore,
MasterTileBuilder: tileBuilder,
DigestStore: &mocks.MockDigestStore{
FirstSeen: time.Now().Unix(),
OkValue: true,
},
DiffStore: mocks.NewMockDiffStore(),
EventBus: eventBus,
}
ixr, err := indexer.New(storages, 10*time.Minute)
assert.NoError(t, err)
idx := ixr.GetIndex()
tile := idx.GetTile(false)
return storages, idx, tile, ixr
}
func loadSample(t assert.TestingT, fileName string) *serialize.Sample {
file, err := os.Open(fileName)
assert.NoError(t, err)
sample, err := serialize.DeserializeSample(file)
assert.NoError(t, err)
return sample
}
// testNameSet collects all test names and the set of digests for
// each test to establish a ground truth for the search below.
func findTests(tile *tiling.Tile, head bool) (map[string]util.StringSet, int) {
testNameSet := map[string]util.StringSet{}
for _, trace := range tile.Traces {
testName := trace.Params()[types.PRIMARY_KEY_FIELD]
if _, ok := testNameSet[testName]; !ok {
testNameSet[testName] = util.StringSet{}
}
vals := trace.(*types.GoldenTrace).Values
if head {
foundVals := []string{}
for i := len(vals) - 1; i >= 0; i-- {
if vals[i] != types.MISSING_DIGEST {
foundVals = vals[i:]
break
}
}
vals = foundVals
}
testNameSet[testName].AddLists(vals)
delete(testNameSet[testName], types.MISSING_DIGEST)
}
total := 0
for _, digests := range testNameSet {
total += len(digests)
}
return testNameSet, total
}