blob: 0b7fd8610a31980343452da8848a15f2fb724a4f [file] [log] [blame]
package worker
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"sync"
"testing"
"time"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/repo_root"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
goldctl_mocks "go.skia.org/infra/gold-client/go/mocks"
"go.skia.org/infra/golden/go/diff/mocks"
"go.skia.org/infra/golden/go/sql"
"go.skia.org/infra/golden/go/sql/databuilder"
dks "go.skia.org/infra/golden/go/sql/datakitchensink"
"go.skia.org/infra/golden/go/sql/schema"
"go.skia.org/infra/golden/go/sql/sqltest"
"go.skia.org/infra/golden/go/types"
)
func TestCalculateDiffs_NoExistingData_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
metricBefore := metrics2.GetCounter("diffcalculator_metricscalculated").Get()
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
imagesToCalculateDiffsFor := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos, dks.DigestA04Unt, dks.DigestA05Unt}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA04Unt, fakeNow),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA05Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA04Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA05Unt, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA05Unt, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA04Unt, fakeNow),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
// 6 distinct pairs of images were compared.
assert.Equal(t, metricBefore+12, metrics2.GetCounter("diffcalculator_metricscalculated").Get())
}
func TestCalculateDiffs_MultipleBatches_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
waitForSystemTime()
// all images loaded will be the same, so diff values are all zeros.
w := newWorkerUsingBlankImages(t, db)
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
var imagesToCalculateDiffsFor []types.Digest
// 16 digests should result in 32 * 32 - 32 diffs (a 32 by 32 square with the diagonal removed)
// This is more than the batchSize in writeMetrics
for i := 0; i < 32; i++ {
d := fmt.Sprintf("%032d", i)
imagesToCalculateDiffsFor = append(imagesToCalculateDiffsFor, types.Digest(d))
}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
row := db.QueryRow(ctx, `SELECT count(*) FROM DiffMetrics`)
count := 0
require.NoError(t, row.Scan(&count))
assert.Equal(t, 32*31, count)
assert.Empty(t, getAllProblemImageRows(t, db))
}
func TestCalculateDiffs_ExistingMetrics_NoExistingTiledTraces_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
existingData := schema.Tables{DiffMetrics: []schema.DiffMetricRow{
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA02Pos), // should not be recomputed
sentinelMetricRow(dks.DigestA02Pos, dks.DigestA01Pos), // should not be recomputed
sentinelMetricRow(dks.DigestBlank, dks.DigestA04Unt), // should not impact computation
sentinelMetricRow(dks.DigestA04Unt, dks.DigestBlank), // should not impact computation
}}
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, existingData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
metricBefore := metrics2.GetCounter("diffcalculator_metricscalculated").Get()
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
imagesToCalculateDiffsFor := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos, dks.DigestA04Unt, dks.DigestA05Unt}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
sentinelMetricRow(dks.DigestBlank, dks.DigestA04Unt),
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA02Pos),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA04Unt, fakeNow),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA05Unt, fakeNow),
sentinelMetricRow(dks.DigestA02Pos, dks.DigestA01Pos),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA04Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA05Unt, fakeNow),
sentinelMetricRow(dks.DigestA04Unt, dks.DigestBlank),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA05Unt, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA04Unt, fakeNow),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
// 5 distinct pairs of images were compared.
assert.Equal(t, metricBefore+10, metrics2.GetCounter("diffcalculator_metricscalculated").Get())
}
func TestCalculateDiffs_NoNewMetrics_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
existingData := schema.Tables{DiffMetrics: []schema.DiffMetricRow{
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA02Pos), // should not be recomputed
sentinelMetricRow(dks.DigestA02Pos, dks.DigestA01Pos), // should not be recomputed
}}
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, existingData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
metricBefore := metrics2.GetCounter("diffcalculator_metricscalculated").Get()
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
imagesToCalculateDiffsFor := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA02Pos),
sentinelMetricRow(dks.DigestA02Pos, dks.DigestA01Pos),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
// no pairs of images were compared.
assert.Equal(t, metricBefore, metrics2.GetCounter("diffcalculator_metricscalculated").Get())
}
func TestCalculateDiffs_ReadFromPrimaryBranch_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
existingData := dks.Build()
// Remove existing diffs, so the ones for triangle test can be recomputed.
existingData.DiffMetrics = nil
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, existingData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
grouping := paramtools.Params{
types.CorpusField: dks.CornersCorpus,
types.PrimaryKeyField: dks.TriangleTest,
}
require.NoError(t, w.CalculateDiffs(ctx, grouping, nil, nil))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestBlank, dks.DigestB01Pos, fakeNow),
expectedFromKS(t, dks.DigestBlank, dks.DigestB02Pos, fakeNow),
expectedFromKS(t, dks.DigestBlank, dks.DigestB03Neg, fakeNow),
expectedFromKS(t, dks.DigestBlank, dks.DigestB04Neg, fakeNow),
expectedFromKS(t, dks.DigestB01Pos, dks.DigestBlank, fakeNow),
expectedFromKS(t, dks.DigestB01Pos, dks.DigestB02Pos, fakeNow),
expectedFromKS(t, dks.DigestB01Pos, dks.DigestB03Neg, fakeNow),
expectedFromKS(t, dks.DigestB01Pos, dks.DigestB04Neg, fakeNow),
expectedFromKS(t, dks.DigestB02Pos, dks.DigestBlank, fakeNow),
expectedFromKS(t, dks.DigestB02Pos, dks.DigestB01Pos, fakeNow),
expectedFromKS(t, dks.DigestB02Pos, dks.DigestB03Neg, fakeNow),
expectedFromKS(t, dks.DigestB02Pos, dks.DigestB04Neg, fakeNow),
expectedFromKS(t, dks.DigestB03Neg, dks.DigestBlank, fakeNow),
expectedFromKS(t, dks.DigestB03Neg, dks.DigestB01Pos, fakeNow),
expectedFromKS(t, dks.DigestB03Neg, dks.DigestB02Pos, fakeNow),
expectedFromKS(t, dks.DigestB03Neg, dks.DigestB04Neg, fakeNow),
expectedFromKS(t, dks.DigestB04Neg, dks.DigestBlank, fakeNow),
expectedFromKS(t, dks.DigestB04Neg, dks.DigestB01Pos, fakeNow),
expectedFromKS(t, dks.DigestB04Neg, dks.DigestB02Pos, fakeNow),
expectedFromKS(t, dks.DigestB04Neg, dks.DigestB03Neg, fakeNow),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
}
func TestCalculateDiffs_ReadFromPrimaryBranch_SparseData_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
// Only C03, C04, C05 will be in the last two tiles for this data.
sparseData := makeSparseData()
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, sparseData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
grouping := paramtools.Params{
types.CorpusField: dks.RoundCorpus,
types.PrimaryKeyField: dks.CircleTest,
}
require.NoError(t, w.CalculateDiffs(ctx, grouping, []types.Digest{dks.DigestC06Pos_CL}, []types.Digest{dks.DigestC06Pos_CL}))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC05Unt, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC06Pos_CL, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC03Unt, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC05Unt, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC06Pos_CL, fakeNow),
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC03Unt, fakeNow),
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC06Pos_CL, fakeNow),
expectedFromKS(t, dks.DigestC06Pos_CL, dks.DigestC03Unt, fakeNow),
expectedFromKS(t, dks.DigestC06Pos_CL, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC06Pos_CL, dks.DigestC05Unt, fakeNow),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
}
func TestCalculateDiffs_ImageNotFound_PartialData(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
waitForSystemTime()
metricBefore := metrics2.GetCounter("diffcalculator_metricscalculated").Get()
// Set up an image source that can download A01 and A02, but returns error on A04
b01, err := ioutil.ReadFile(filepath.Join(kitchenSinkRoot(t), string(dks.DigestA01Pos)+".png"))
require.NoError(t, err)
b02, err := ioutil.ReadFile(filepath.Join(kitchenSinkRoot(t), string(dks.DigestA02Pos)+".png"))
require.NoError(t, err)
mis := &mocks.ImageSource{}
mis.On("GetImage", testutils.AnyContext, dks.DigestA01Pos).Return(b01, nil)
mis.On("GetImage", testutils.AnyContext, dks.DigestA02Pos).Return(b02, nil)
mis.On("GetImage", testutils.AnyContext, dks.DigestA04Unt).Return(nil, errors.New("not found"))
w := New(db, mis, &memDiffCache{}, 2)
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
imagesToCalculateDiffsFor := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos, dks.DigestA04Unt}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
actualMetrics := getAllDiffMetricRows(t, db)
// We should see partial success
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA01Pos, fakeNow),
}, actualMetrics)
actualProblemImageRows := getAllProblemImageRows(t, db)
require.Len(t, actualProblemImageRows, 1)
problem := actualProblemImageRows[0]
assert.Equal(t, string(dks.DigestA04Unt), problem.Digest)
// could be more than 1 because of multiple goroutines running
assert.True(t, problem.NumErrors >= 1)
assert.Contains(t, problem.LatestError, "not found")
assert.Equal(t, fakeNow, problem.ErrorTS)
// One pair of images was compared successfully.
assert.Equal(t, metricBefore+2, metrics2.GetCounter("diffcalculator_metricscalculated").Get())
}
func TestCalculateDiffs_CorruptedImage_PartialData(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 2, 2, 2, 2, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
// Write a sentinel error for A04. We expect this to be updated.
existingProblem := schema.Tables{ProblemImages: []schema.ProblemImageRow{{
Digest: string(dks.DigestA04Unt),
NumErrors: 100,
LatestError: "not found",
ErrorTS: time.Date(2020, time.January, 1, 1, 1, 1, 0, time.UTC),
}}}
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, existingProblem))
waitForSystemTime()
// Set up an image source that can download A01 and A02, but invalid PNG data for A04
b01, err := ioutil.ReadFile(filepath.Join(kitchenSinkRoot(t), string(dks.DigestA01Pos)+".png"))
require.NoError(t, err)
b02, err := ioutil.ReadFile(filepath.Join(kitchenSinkRoot(t), string(dks.DigestA02Pos)+".png"))
require.NoError(t, err)
mis := &mocks.ImageSource{}
mis.On("GetImage", testutils.AnyContext, dks.DigestA01Pos).Return(b01, nil)
mis.On("GetImage", testutils.AnyContext, dks.DigestA02Pos).Return(b02, nil)
mis.On("GetImage", testutils.AnyContext, dks.DigestA04Unt).Return([]byte(`not a png`), nil)
w := New(db, mis, &memDiffCache{}, 2)
grouping := paramtools.Params{
types.CorpusField: "not used",
types.PrimaryKeyField: "not used",
}
imagesToCalculateDiffsFor := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos, dks.DigestA04Unt}
require.NoError(t, w.CalculateDiffs(ctx, grouping, imagesToCalculateDiffsFor, imagesToCalculateDiffsFor))
actualMetrics := getAllDiffMetricRows(t, db)
// We should see partial success
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA01Pos, fakeNow),
}, actualMetrics)
actualProblemImageRows := getAllProblemImageRows(t, db)
require.Len(t, actualProblemImageRows, 1)
problem := actualProblemImageRows[0]
assert.Equal(t, string(dks.DigestA04Unt), problem.Digest)
// The sentinel value is 100. This should be greater than that because of the new error.
assert.True(t, problem.NumErrors >= 101)
assert.Contains(t, problem.LatestError, "png: invalid format: not a PNG file")
assert.Equal(t, fakeNow, problem.ErrorTS)
}
func TestCalculateDiffs_LeftDigestsComparedOnlyToRightDigests_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
existingData := schema.Tables{DiffMetrics: []schema.DiffMetricRow{
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA05Unt), // should not be recomputed
sentinelMetricRow(dks.DigestA05Unt, dks.DigestA01Pos), // should not be recomputed
}}
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, existingData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
metricBefore := metrics2.GetCounter("diffcalculator_metricscalculated").Get()
grouping := paramtools.Params{
types.CorpusField: "not",
types.PrimaryKeyField: "used",
}
leftDigests := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos,
dks.DigestA04Unt, dks.DigestA05Unt, dks.DigestA06Unt}
rightDigests := []types.Digest{dks.DigestA01Pos, dks.DigestA02Pos}
require.NoError(t, w.CalculateDiffs(ctx, grouping, leftDigests, rightDigests))
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA04Unt, fakeNow),
sentinelMetricRow(dks.DigestA01Pos, dks.DigestA05Unt),
expectedFromKS(t, dks.DigestA01Pos, dks.DigestA06Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA04Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA05Unt, fakeNow),
expectedFromKS(t, dks.DigestA02Pos, dks.DigestA06Unt, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA04Unt, dks.DigestA02Pos, fakeNow),
sentinelMetricRow(dks.DigestA05Unt, dks.DigestA01Pos),
expectedFromKS(t, dks.DigestA05Unt, dks.DigestA02Pos, fakeNow),
expectedFromKS(t, dks.DigestA06Unt, dks.DigestA01Pos, fakeNow),
expectedFromKS(t, dks.DigestA06Unt, dks.DigestA02Pos, fakeNow),
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
// 6 pairs of images calculated, one (02+05) in both directions.
assert.Equal(t, metricBefore+7, metrics2.GetCounter("diffcalculator_metricscalculated").Get())
}
func TestCalculateDiffs_IgnoredTracesNotComparedToEachOther_Success(t *testing.T) {
unittest.LargeTest(t)
fakeNow := time.Date(2021, time.February, 1, 1, 1, 1, 0, time.UTC)
ctx := context.WithValue(context.Background(), NowSourceKey, mockTime(fakeNow))
db := sqltest.NewCockroachDBForTestsWithProductionSchema(ctx, t)
// C04 and C05 appear only on ignored traces. C01, C02 appear only on non-ignored traces.
// C03 appears on both an ignored trace and an ignored trace.
sparseData := makeDataWithIgnoredTraces()
require.NoError(t, sqltest.BulkInsertDataTables(ctx, db, sparseData))
waitForSystemTime()
w := newWorkerUsingImagesFromKitchenSink(t, db)
grouping := paramtools.Params{
types.CorpusField: dks.RoundCorpus,
types.PrimaryKeyField: dks.CircleTest,
}
require.NoError(t, w.CalculateDiffs(ctx, grouping, nil, nil))
// The images that are only on ignored traces should not be compared to each other.
actualMetrics := getAllDiffMetricRows(t, db)
assert.Equal(t, []schema.DiffMetricRow{
expectedFromKS(t, dks.DigestC01Pos, dks.DigestC02Pos, fakeNow),
expectedFromKS(t, dks.DigestC01Pos, dks.DigestC03Unt, fakeNow),
expectedFromKS(t, dks.DigestC01Pos, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC01Pos, dks.DigestC05Unt, fakeNow),
expectedFromKS(t, dks.DigestC02Pos, dks.DigestC01Pos, fakeNow),
expectedFromKS(t, dks.DigestC02Pos, dks.DigestC03Unt, fakeNow),
expectedFromKS(t, dks.DigestC02Pos, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC02Pos, dks.DigestC05Unt, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC01Pos, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC02Pos, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC04Unt, fakeNow),
expectedFromKS(t, dks.DigestC03Unt, dks.DigestC05Unt, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC01Pos, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC02Pos, fakeNow),
expectedFromKS(t, dks.DigestC04Unt, dks.DigestC03Unt, fakeNow),
// expectedFromKS(t, dks.DigestC04Unt, dks.DigestC05Unt, fakeNow), skipped
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC01Pos, fakeNow),
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC02Pos, fakeNow),
expectedFromKS(t, dks.DigestC05Unt, dks.DigestC03Unt, fakeNow),
// expectedFromKS(t, dks.DigestC05Unt, dks.DigestC04Unt, fakeNow), skipped
}, actualMetrics)
assert.Empty(t, getAllProblemImageRows(t, db))
}
func getAllDiffMetricRows(t *testing.T, db *pgxpool.Pool) []schema.DiffMetricRow {
rows, err := db.Query(context.Background(), `SELECT * FROM DiffMetrics ORDER BY left_digest, right_digest`)
require.NoError(t, err)
defer rows.Close()
var actualMetrics []schema.DiffMetricRow
for rows.Next() {
var m schema.DiffMetricRow
require.NoError(t, rows.Scan(&m.LeftDigest, &m.RightDigest, &m.NumPixelsDiff, &m.PercentPixelsDiff,
&m.MaxRGBADiffs, &m.MaxChannelDiff, &m.CombinedMetric, &m.DimensionsDiffer, &m.Timestamp))
m.Timestamp = m.Timestamp.UTC()
actualMetrics = append(actualMetrics, m)
// spot check that we handle arrays correctly.
assert.NotEqual(t, [4]int{0, 0, 0, 0}, m.MaxRGBADiffs)
}
return actualMetrics
}
func getAllProblemImageRows(t *testing.T, db *pgxpool.Pool) []schema.ProblemImageRow {
rows, err := db.Query(context.Background(), `SELECT * FROM ProblemImages ORDER BY digest`)
require.NoError(t, err)
defer rows.Close()
var actualRows []schema.ProblemImageRow
for rows.Next() {
var r schema.ProblemImageRow
require.NoError(t, rows.Scan(&r.Digest, &r.NumErrors, &r.LatestError, &r.ErrorTS))
r.ErrorTS = r.ErrorTS.UTC()
actualRows = append(actualRows, r)
}
return actualRows
}
func makeSparseData() schema.Tables {
b := databuilder.TablesBuilder{}
// Make a few commits with data across several tiles (tile width == 100)
b.CommitsWithData().
Insert(337, "whomever", "commit 337", "2020-12-01T00:00:01Z").
Insert(437, "whomever", "commit 437", "2020-12-01T00:00:02Z").
Insert(537, "whomever", "commit 537", "2020-12-01T00:00:03Z").
Insert(637, "whomever", "commit 637", "2020-12-01T00:00:04Z").
Insert(687, "whomever", "commit 687", "2020-12-01T00:00:05Z")
// Then add a bunch of empty commits after. These should not impact the latest commit/tile
// with data.
nd := b.CommitsWithNoData()
for i := 688; i < 710; i++ {
nd.Insert(i, "no data author", "no data "+strconv.Itoa(i), "2020-12-01T00:00:06Z")
}
b.SetDigests(map[rune]types.Digest{
// All of these will be untriaged because diffs don't care about triage status
'a': dks.DigestC01Pos,
'b': dks.DigestC02Pos,
'c': dks.DigestC03Unt,
'd': dks.DigestC04Unt,
'e': dks.DigestC05Unt,
})
b.SetGroupingKeys(types.CorpusField, types.PrimaryKeyField)
b.AddTracesWithCommonKeys(paramtools.Params{
types.CorpusField: dks.RoundCorpus,
}).History("abcde").Keys([]paramtools.Params{{types.PrimaryKeyField: dks.CircleTest}}).
OptionsAll(paramtools.Params{"ext": "png"}).
IngestedFrom([]string{"file1", "file2", "file3", "file4", "file5"}, // not used in this test
[]string{"2020-12-01T00:00:05Z", "2020-12-01T00:00:05Z", "2020-12-01T00:00:05Z", "2020-12-01T00:00:05Z", "2020-12-01T00:00:05Z"})
b.AddIgnoreRule("userOne", "userOne", "2020-12-02T0:00:00Z", "nop ignore",
paramtools.ParamSet{"matches": []string{"nothing"}})
rv := b.Build()
rv.DiffMetrics = nil
return rv
}
func makeDataWithIgnoredTraces() schema.Tables {
b := databuilder.TablesBuilder{}
b.CommitsWithData().
Insert(8, "whomever", "commit 8", "2020-12-01T00:00:01Z").
Append("whomever", "commit 9", "2020-12-01T00:00:02Z")
b.SetDigests(map[rune]types.Digest{
// All of these will be untriaged because diffs don't care about triage status
'a': dks.DigestC01Pos,
'b': dks.DigestC02Pos,
'c': dks.DigestC03Unt,
'd': dks.DigestC04Unt,
'e': dks.DigestC05Unt,
})
b.SetGroupingKeys(types.CorpusField, types.PrimaryKeyField)
b.AddTracesWithCommonKeys(paramtools.Params{
types.CorpusField: dks.RoundCorpus,
types.PrimaryKeyField: dks.CircleTest,
}).History(
"aa",
"bb",
"cb",
"cd", // ignored
"ee", // ignored
).Keys([]paramtools.Params{
{"device": "one"},
{"device": "two"},
{"device": "three"},
{"device": "four"},
{"device": "five"},
}).OptionsAll(paramtools.Params{"ext": "png"}).
IngestedFrom([]string{"file1", "file2"}, // not used in this test
[]string{"2020-12-01T00:00:05Z", "2020-12-01T00:00:05Z"})
b.AddIgnoreRule("userOne", "userOne", "2020-12-02T0:00:00Z", "buggy",
paramtools.ParamSet{"device": []string{"four", "five"}})
rv := b.Build()
rv.DiffMetrics = nil
return rv
}
// sentinelMetricRow returns a DiffMetricRow with arbitrary data that should not change as a result
// of the test using it (DiffMetrics are immutable).
func sentinelMetricRow(left types.Digest, right types.Digest) schema.DiffMetricRow {
return schema.DiffMetricRow{
LeftDigest: d(left),
RightDigest: d(right),
NumPixelsDiff: -1,
PercentPixelsDiff: -1,
MaxRGBADiffs: [4]int{-1, -1, -1, -1},
MaxChannelDiff: -1,
CombinedMetric: -1,
Timestamp: time.Date(2020, time.March, 14, 15, 9, 26, 0, time.UTC),
}
}
func d(d types.Digest) schema.DigestBytes {
b, err := sql.DigestToBytes(d)
if err != nil {
panic(err)
}
return b
}
func kitchenSinkRoot(t *testing.T) string {
root, err := repo_root.Get()
if err != nil {
require.NoError(t, err)
}
return filepath.Join(root, "golden", "go", "sql", "datakitchensink", "img")
}
func newWorkerUsingImagesFromKitchenSink(t *testing.T, db *pgxpool.Pool) *WorkerImpl {
return New(db, &fsImageSource{root: kitchenSinkRoot(t)}, &memDiffCache{}, 2)
}
func newWorkerUsingBlankImages(t *testing.T, db *pgxpool.Pool) *WorkerImpl {
infraRoot, err := repo_root.Get()
require.NoError(t, err)
blankImagePath := filepath.Join(infraRoot, "golden", "go", "sql", "datakitchensink", "img", string(dks.DigestBlank+".png"))
return New(db, &fixedImageSource{img: blankImagePath}, &memDiffCache{}, 2)
}
var kitchenSinkData = dks.Build()
// expectedFromKS returns the computed diff metric from the kitchen sink data. It replaces the
// default timestamp with the provided timestamp.
func expectedFromKS(t *testing.T, left types.Digest, right types.Digest, ts time.Time) schema.DiffMetricRow {
leftB := d(left)
rightB := d(right)
for _, row := range kitchenSinkData.DiffMetrics {
if bytes.Equal(leftB, row.LeftDigest) && bytes.Equal(rightB, row.RightDigest) {
row.Timestamp = ts
return row
}
}
require.Fail(t, "Could not find diff metrics for %s-%s", left, right)
return schema.DiffMetricRow{}
}
// waitForSystemTime waits for a time greater than the duration mentioned in "AS OF SYSTEM TIME"
// clauses in queries. This way, the queries will be accurate.
func waitForSystemTime() {
time.Sleep(150 * time.Millisecond)
}
func mockTime(ts time.Time) NowSource {
mt := goldctl_mocks.NowSource{}
mt.On("Now").Return(ts)
return &mt
}
// fsImageSource returns an image from the local file system, looking in a given root directory.
type fsImageSource struct {
root string
}
func (f fsImageSource) GetImage(_ context.Context, digest types.Digest) ([]byte, error) {
p := filepath.Join(f.root, string(digest)+".png")
return ioutil.ReadFile(p)
}
// fixedImageSource returns the same bytes for every image
type fixedImageSource struct {
img string
}
func (f fixedImageSource) GetImage(_ context.Context, _ types.Digest) ([]byte, error) {
return ioutil.ReadFile(f.img)
}
type memDiffCache struct {
mutex sync.Mutex
data map[string]bool
}
func (m *memDiffCache) RemoveAlreadyComputedDiffs(_ context.Context, left types.Digest, rightDigests []types.Digest) []types.Digest {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.data == nil {
m.data = map[string]bool{}
}
var rv []types.Digest
for _, right := range rightDigests {
if !m.data[string(left+right)] {
rv = append(rv, right)
}
}
return rv
}
func (m *memDiffCache) StoreDiffComputed(_ context.Context, left, right types.Digest) {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.data == nil {
m.data = map[string]bool{}
}
m.data[string(left+right)] = true
}