[gold] Refactor and simplify the ignorestore implementation.
There's a nasty cyclical dependency between ignorestore and storage
which was papered over long ago. This removes the dependency, which
will make the refactoring effort in:
https://skia-review.googlesource.com/c/buildbot/+/219477
possible again.
Bug: skia:9096
Change-Id: Ieb96dfafde8f4a34d99a3fb0da9f9a158b6a4f52
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/219480
Reviewed-by: Ben Wagner aka dogben <benjaminwagner@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/golden/cmd/sampler/main.go b/golden/cmd/sampler/main.go
index d8d35f2..061894e 100644
--- a/golden/cmd/sampler/main.go
+++ b/golden/cmd/sampler/main.go
@@ -8,7 +8,6 @@
"net/url"
"os"
"reflect"
- "time"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/common"
@@ -23,6 +22,7 @@
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/expstorage/fs_expstore"
"go.skia.org/infra/golden/go/ignore"
+ "go.skia.org/infra/golden/go/ignore/ds_ignorestore"
"go.skia.org/infra/golden/go/serialize"
"go.skia.org/infra/golden/go/storage"
"go.skia.org/infra/golden/go/types"
@@ -115,7 +115,7 @@
// Get the ignore rules.
var err error
- if sample.IgnoreRules, err = ignoreStore.List(false); err != nil {
+ if sample.IgnoreRules, err = ignoreStore.List(); err != nil {
sklog.Fatalf("Error retrieving ignore rules: %s", err)
}
@@ -219,7 +219,7 @@
EventBus: evt,
}
- storages.IgnoreStore, err = ignore.NewCloudIgnoreStore(ds.DS, expStore, storages.GetTileStreamNow(time.Minute*20, "sampler-ignore-store"))
+ storages.IgnoreStore, err = ds_ignorestore.New(ds.DS)
if err != nil {
sklog.Fatalf("Unable to create cloud ignorestore: %s", err)
}
diff --git a/golden/cmd/skiacorrectness/main.go b/golden/cmd/skiacorrectness/main.go
index d8d8c81..b21288b 100644
--- a/golden/cmd/skiacorrectness/main.go
+++ b/golden/cmd/skiacorrectness/main.go
@@ -47,6 +47,7 @@
"go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
"go.skia.org/infra/golden/go/expstorage/fs_expstore"
"go.skia.org/infra/golden/go/ignore"
+ "go.skia.org/infra/golden/go/ignore/ds_ignorestore"
"go.skia.org/infra/golden/go/indexer"
"go.skia.org/infra/golden/go/search"
"go.skia.org/infra/golden/go/shared"
@@ -424,10 +425,7 @@
// openSite indicates whether this can expose all end-points. The user still has to be authenticated.
openSite := (*pubWhiteList == WHITELIST_ALL) || *forceLogin
- // TODO(kjlubick): storage.Storage contains an IgnoreStore, so we can't pass ignore
- // a storage.Storage directly. There probably shouldn't be one monolithic storage object,
- // but each package could define its own.
- if storages.IgnoreStore, err = ignore.NewCloudIgnoreStore(ds.DS, storages.ExpectationsStore, storages.GetTileStreamNow(time.Minute, "gold-ignore-store")); err != nil {
+ if storages.IgnoreStore, err = ds_ignorestore.New(ds.DS); err != nil {
sklog.Fatalf("Unable to create cloud ignorestore: %s", err)
}
diff --git a/golden/go/diffstore/bench_test.go b/golden/go/diffstore/bench_test.go
index 13fd6cd..c14feae 100644
--- a/golden/go/diffstore/bench_test.go
+++ b/golden/go/diffstore/bench_test.go
@@ -10,7 +10,7 @@
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
- "go.skia.org/infra/golden/go/ignore"
+ "go.skia.org/infra/golden/go/ignore/mem_ignorestore"
"go.skia.org/infra/golden/go/mocks"
"go.skia.org/infra/golden/go/serialize"
"go.skia.org/infra/golden/go/types"
@@ -28,7 +28,7 @@
client := mocks.GetHTTPClient(b)
defer testutils.RemoveAll(b, baseDir)
- memIgnoreStore := ignore.NewMemIgnoreStore()
+ memIgnoreStore := mem_ignorestore.New()
for _, ir := range sample.IgnoreRules {
assert.NoError(b, memIgnoreStore.Create(ir))
}
diff --git a/golden/go/ignore/cloud_ignorestore.go b/golden/go/ignore/cloud_ignorestore.go
deleted file mode 100644
index 1e68bfe..0000000
--- a/golden/go/ignore/cloud_ignorestore.go
+++ /dev/null
@@ -1,249 +0,0 @@
-package ignore
-
-import (
- "context"
- "fmt"
- "sort"
- "sync/atomic"
-
- "cloud.google.com/go/datastore"
- "go.skia.org/infra/go/ds"
- "go.skia.org/infra/go/skerr"
- "go.skia.org/infra/go/sklog"
- "go.skia.org/infra/golden/go/dsutil"
- "go.skia.org/infra/golden/go/expstorage"
- "go.skia.org/infra/golden/go/types"
- "golang.org/x/sync/errgroup"
-)
-
-// cloudIgnoreStore implements the IgnoreStore interface
-type cloudIgnoreStore struct {
- client *datastore.Client
- recentKeysList *dsutil.RecentKeysList
- revision int64
- lastCpxTile types.ComplexTile
- expStore expstorage.ExpectationsStore
- cpxTileStream <-chan types.ComplexTile
-}
-
-// NewCloudIgnoreStore returns an IgnoreStore instance that is backed by Cloud Datastore.
-func NewCloudIgnoreStore(client *datastore.Client, expStore expstorage.ExpectationsStore, tileStream <-chan types.ComplexTile) (IgnoreStore, error) {
- if client == nil {
- return nil, skerr.Fmt("Received nil for datastore client.")
- }
-
- containerKey := ds.NewKey(ds.HELPER_RECENT_KEYS)
- containerKey.Name = "ignore:recent-keys"
-
- store := &cloudIgnoreStore{
- client: client,
- recentKeysList: dsutil.NewRecentKeysList(client, containerKey, dsutil.DefaultConsistencyDelta),
- expStore: expStore,
- cpxTileStream: tileStream,
- }
- return store, nil
-}
-
-// Create implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) Create(ignoreRule *IgnoreRule) error {
- createFn := func(tx *datastore.Transaction) error {
- key := dsutil.TimeSortableKey(ds.IGNORE_RULE, 0)
- ignoreRule.ID = key.ID
-
- // Add the new rule and put its key with the recently added keys.
- if _, err := tx.Put(key, ignoreRule); err != nil {
- return err
- }
-
- return c.recentKeysList.Add(tx, key)
- }
-
- // Run the relevant updates in a transaction.
- _, err := c.client.RunInTransaction(context.TODO(), createFn)
-
- // TODO(stephana): Look into removing the revision feature. I don't think
- // this is really necessary going forward.
-
- if err == nil {
- atomic.AddInt64(&c.revision, 1)
- }
- return err
-}
-
-// List implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) List(addCounts bool) ([]*IgnoreRule, error) {
- ctx := context.TODO()
- var egroup errgroup.Group
- var queriedKeys []*datastore.Key
- egroup.Go(func() error {
- // Query all entities.
- query := ds.NewQuery(ds.IGNORE_RULE).KeysOnly()
- var err error
- queriedKeys, err = c.client.GetAll(ctx, query, nil)
- return err
- })
-
- var recently *dsutil.Recently
- egroup.Go(func() error {
- var err error
- recently, err = c.recentKeysList.GetRecent()
- return err
- })
-
- if err := egroup.Wait(); err != nil {
- return nil, skerr.Fmt("Error getting keys of ignore rules: %s", err)
- }
-
- // Merge the keys to get all of the current keys.
- allKeys := recently.Combine(queriedKeys)
- if len(allKeys) == 0 {
- return []*IgnoreRule{}, nil
- }
-
- ret := make([]*IgnoreRule, len(allKeys))
- if err := c.client.GetMulti(ctx, allKeys, ret); err != nil {
- return nil, err
- }
- sort.Slice(ret, func(i, j int) bool { return ret[i].Expires.Before(ret[j].Expires) })
-
- if addCounts {
- var err error
- c.lastCpxTile, err = addIgnoreCounts(ret, c, c.lastCpxTile, c.expStore, c.cpxTileStream)
- if err != nil {
- sklog.Errorf("Unable to add counts to ignore list result: %s", err)
- }
- }
-
- return ret, nil
-}
-
-// Update implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) Update(id int64, rule *IgnoreRule) error {
- ctx := context.TODO()
- key := ds.NewKey(ds.IGNORE_RULE)
- key.ID = id
- _, err := c.client.Mutate(ctx, datastore.NewUpdate(key, rule))
- if err == nil {
- atomic.AddInt64(&c.revision, 1)
- }
- return err
-}
-
-// Delete implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) Delete(id int64) (int, error) {
- if id <= 0 {
- return 0, skerr.Fmt("Given id does not exist: %d", id)
- }
-
- deleteFn := func(tx *datastore.Transaction) error {
- key := ds.NewKey(ds.IGNORE_RULE)
- key.ID = id
-
- ignoreRule := &IgnoreRule{}
- if err := tx.Get(key, ignoreRule); err != nil {
- return err
- }
-
- if err := tx.Delete(key); err != nil {
- return err
- }
-
- return c.recentKeysList.Delete(tx, key)
- }
-
- // Run the relevant updates in a transaction.
- _, err := c.client.RunInTransaction(context.TODO(), deleteFn)
- if err != nil {
- // Don't report an error if the item did not exist.
- if err == datastore.ErrNoSuchEntity {
- sklog.Warningf("Could not delete ignore with id %d because it did not exist", id)
- return 0, nil
- }
- return 0, err
- }
-
- atomic.AddInt64(&c.revision, 1)
- return 1, nil
-}
-
-// Revision implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) Revision() int64 {
- return atomic.LoadInt64(&c.revision)
-}
-
-// BuildRuleMatcher implements the IgnoreStore interface.
-func (c *cloudIgnoreStore) BuildRuleMatcher() (RuleMatcher, error) {
- return buildRuleMatcher(c)
-}
-
-// TODO(kjlubick): Add unit tests to addIgnoreCounts using mocks
-
-// addIgnoreCounts adds counts for the current tile to the given list of rules.
-func addIgnoreCounts(rules []*IgnoreRule, ignoreStore IgnoreStore, lastCpxTile types.ComplexTile, expStore expstorage.ExpectationsStore, tileStream <-chan types.ComplexTile) (types.ComplexTile, error) {
- if (expStore == nil) || (tileStream == nil) {
- return nil, fmt.Errorf("Either expStore or tileStream is nil. Cannot count ignores.")
- }
-
- exp, err := expStore.Get()
- if err != nil {
- return nil, err
- }
-
- ignoreMatcher, err := ignoreStore.BuildRuleMatcher()
- if err != nil {
- return nil, err
- }
-
- // Get the next tile.
- var cpxTile types.ComplexTile = nil
- select {
- case cpxTile = <-tileStream:
- default:
- cpxTile = lastCpxTile
- }
- if cpxTile == nil {
- return nil, fmt.Errorf("No tile available to count ignores")
- }
-
- // Count the untriaged digests in HEAD.
- // matchingDigests[rule.ID]map["testname:digest"]bool
- matchingDigests := make(map[int64]map[string]bool, len(rules))
- rulesByDigest := map[string]map[int64]bool{}
- tileWithIgnores := cpxTile.GetTile(types.IncludeIgnoredTraces)
- for _, trace := range tileWithIgnores.Traces {
- gTrace := trace.(*types.GoldenTrace)
- if matchRules, ok := ignoreMatcher(gTrace.Keys); ok {
- testName := gTrace.TestName()
- if digest := gTrace.LastDigest(); digest != types.MISSING_DIGEST && (exp.Classification(testName, digest) == types.UNTRIAGED) {
- k := string(testName) + ":" + string(digest)
- for _, r := range matchRules {
- // Add the digest to all matching rules.
- if t, ok := matchingDigests[r.ID]; ok {
- t[k] = true
- } else {
- matchingDigests[r.ID] = map[string]bool{k: true}
- }
-
- // Add the rule to the test-digest.
- if t, ok := rulesByDigest[k]; ok {
- t[r.ID] = true
- } else {
- rulesByDigest[k] = map[int64]bool{r.ID: true}
- }
- }
- }
- }
- }
-
- for _, r := range rules {
- r.Count = len(matchingDigests[r.ID])
- r.ExclusiveCount = 0
- for testDigestKey := range matchingDigests[r.ID] {
- // If exactly this one rule matches then account for it.
- if len(rulesByDigest[testDigestKey]) == 1 {
- r.ExclusiveCount++
- }
- }
- }
- return cpxTile, nil
-}
diff --git a/golden/go/ignore/common.go b/golden/go/ignore/common.go
index e816468..2611adc 100644
--- a/golden/go/ignore/common.go
+++ b/golden/go/ignore/common.go
@@ -2,8 +2,8 @@
import "net/url"
-func buildRuleMatcher(store IgnoreStore) (RuleMatcher, error) {
- rulesList, err := store.List(false)
+func BuildRuleMatcher(store IgnoreStore) (RuleMatcher, error) {
+ rulesList, err := store.List()
if err != nil {
return noopRuleMatcher, err
}
diff --git a/golden/go/ignore/ds_ignorestore/ds_ignorestore.go b/golden/go/ignore/ds_ignorestore/ds_ignorestore.go
new file mode 100644
index 0000000..038209b
--- /dev/null
+++ b/golden/go/ignore/ds_ignorestore/ds_ignorestore.go
@@ -0,0 +1,162 @@
+package ds_ignorestore
+
+import (
+ "context"
+ "sort"
+ "sync/atomic"
+
+ "cloud.google.com/go/datastore"
+ "go.skia.org/infra/go/ds"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/golden/go/dsutil"
+ "go.skia.org/infra/golden/go/ignore"
+ "golang.org/x/sync/errgroup"
+)
+
+// DSIgnoreStore implements the IgnoreStore interface
+type DSIgnoreStore struct {
+ client *datastore.Client
+ recentKeysList *dsutil.RecentKeysList
+ revision int64
+}
+
+// New returns an IgnoreStore instance that is backed by Cloud Datastore.
+func New(client *datastore.Client) (*DSIgnoreStore, error) {
+ if client == nil {
+ return nil, skerr.Fmt("Received nil for datastore client.")
+ }
+
+ containerKey := ds.NewKey(ds.HELPER_RECENT_KEYS)
+ containerKey.Name = "ignore:recent-keys"
+
+ store := &DSIgnoreStore{
+ client: client,
+ recentKeysList: dsutil.NewRecentKeysList(client, containerKey, dsutil.DefaultConsistencyDelta),
+ }
+ return store, nil
+}
+
+// Create implements the IgnoreStore interface.
+func (c *DSIgnoreStore) Create(ignoreRule *ignore.IgnoreRule) error {
+ createFn := func(tx *datastore.Transaction) error {
+ key := dsutil.TimeSortableKey(ds.IGNORE_RULE, 0)
+ ignoreRule.ID = key.ID
+
+ // Add the new rule and put its key with the recently added keys.
+ if _, err := tx.Put(key, ignoreRule); err != nil {
+ return err
+ }
+
+ return c.recentKeysList.Add(tx, key)
+ }
+
+ // Run the relevant updates in a transaction.
+ _, err := c.client.RunInTransaction(context.TODO(), createFn)
+
+ // TODO(stephana): Look into removing the revision feature. I don't think
+ // this is really necessary going forward.
+
+ if err == nil {
+ atomic.AddInt64(&c.revision, 1)
+ }
+ return err
+}
+
+// List implements the IgnoreStore interface.
+func (c *DSIgnoreStore) List() ([]*ignore.IgnoreRule, error) {
+ ctx := context.TODO()
+ var egroup errgroup.Group
+ var queriedKeys []*datastore.Key
+ egroup.Go(func() error {
+ // Query all entities.
+ query := ds.NewQuery(ds.IGNORE_RULE).KeysOnly()
+ var err error
+ queriedKeys, err = c.client.GetAll(ctx, query, nil)
+ return err
+ })
+
+ var recently *dsutil.Recently
+ egroup.Go(func() error {
+ var err error
+ recently, err = c.recentKeysList.GetRecent()
+ return err
+ })
+
+ if err := egroup.Wait(); err != nil {
+ return nil, skerr.Fmt("Error getting keys of ignore rules: %s", err)
+ }
+
+ // Merge the keys to get all of the current keys.
+ allKeys := recently.Combine(queriedKeys)
+ if len(allKeys) == 0 {
+ return []*ignore.IgnoreRule{}, nil
+ }
+
+ ret := make([]*ignore.IgnoreRule, len(allKeys))
+ if err := c.client.GetMulti(ctx, allKeys, ret); err != nil {
+ return nil, err
+ }
+ sort.Slice(ret, func(i, j int) bool { return ret[i].Expires.Before(ret[j].Expires) })
+
+ return ret, nil
+}
+
+// Update implements the IgnoreStore interface.
+func (c *DSIgnoreStore) Update(id int64, rule *ignore.IgnoreRule) error {
+ ctx := context.TODO()
+ key := ds.NewKey(ds.IGNORE_RULE)
+ key.ID = id
+ _, err := c.client.Mutate(ctx, datastore.NewUpdate(key, rule))
+ if err == nil {
+ atomic.AddInt64(&c.revision, 1)
+ }
+ return err
+}
+
+// Delete implements the IgnoreStore interface.
+func (c *DSIgnoreStore) Delete(id int64) (int, error) {
+ if id <= 0 {
+ return 0, skerr.Fmt("Given id does not exist: %d", id)
+ }
+
+ deleteFn := func(tx *datastore.Transaction) error {
+ key := ds.NewKey(ds.IGNORE_RULE)
+ key.ID = id
+
+ ignoreRule := &ignore.IgnoreRule{}
+ if err := tx.Get(key, ignoreRule); err != nil {
+ return err
+ }
+
+ if err := tx.Delete(key); err != nil {
+ return err
+ }
+
+ return c.recentKeysList.Delete(tx, key)
+ }
+
+ // Run the relevant updates in a transaction.
+ _, err := c.client.RunInTransaction(context.TODO(), deleteFn)
+ if err != nil {
+ // Don't report an error if the item did not exist.
+ if err == datastore.ErrNoSuchEntity {
+ sklog.Warningf("Could not delete ignore with id %d because it did not exist", id)
+ return 0, nil
+ }
+ return 0, err
+ }
+
+ atomic.AddInt64(&c.revision, 1)
+ return 1, nil
+}
+
+// Revision implements the IgnoreStore interface.
+func (c *DSIgnoreStore) Revision() int64 {
+ return atomic.LoadInt64(&c.revision)
+}
+
+// BuildRuleMatcher implements the IgnoreStore interface.
+func (c *DSIgnoreStore) BuildRuleMatcher() (ignore.RuleMatcher, error) {
+ return ignore.BuildRuleMatcher(c)
+}
diff --git a/golden/go/ignore/ds_ignorestore/ds_ignorestore_test.go b/golden/go/ignore/ds_ignorestore/ds_ignorestore_test.go
new file mode 100644
index 0000000..0e1e14e
--- /dev/null
+++ b/golden/go/ignore/ds_ignorestore/ds_ignorestore_test.go
@@ -0,0 +1,23 @@
+package ds_ignorestore
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/ds"
+ ds_testutil "go.skia.org/infra/go/ds/testutil"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/ignore/testutils"
+)
+
+func TestCloudIgnoreStore(t *testing.T) {
+ unittest.LargeTest(t)
+
+ // Run to the locally running emulator.
+ cleanup := ds_testutil.InitDatastore(t, ds.IGNORE_RULE, ds.HELPER_RECENT_KEYS)
+ defer cleanup()
+
+ store, err := New(ds.DS)
+ assert.NoError(t, err)
+ testutils.IgnoreStoreAll(t, store)
+}
diff --git a/golden/go/ignore/ignorestore.go b/golden/go/ignore/ignorestore.go
index 89cff4f..82c63d9 100644
--- a/golden/go/ignore/ignorestore.go
+++ b/golden/go/ignore/ignorestore.go
@@ -3,7 +3,6 @@
import (
"fmt"
"net/url"
- "sync"
"time"
)
@@ -19,12 +18,7 @@
Create(*IgnoreRule) error
// List returns all ignore rules in the ignore store.
- // 'addCounts' indicates whether to include counts how often an ignore
- // rule appears in the current tile.
- // TODO(kjlubick): Remove the addCounts flag in the signature of the List function
- // and expose the AddIgnoreCounts function. This would remove the expsStore and
- // tileStream members of the cloudIgnoreStore struct and simplify the interface.
- List(addCounts bool) ([]*IgnoreRule, error)
+ List() ([]*IgnoreRule, error)
// Updates an IgnoreRule.
Update(id int64, rule *IgnoreRule) error
@@ -46,14 +40,12 @@
// IgnoreRule is the GUI struct for dealing with Ignore rules.
type IgnoreRule struct {
- ID int64 `json:"id,string"`
- Name string `json:"name"`
- UpdatedBy string `json:"updatedBy"`
- Expires time.Time `json:"expires"`
- Query string `json:"query"`
- Note string `json:"note"`
- Count int `json:"count" datastore:"-"`
- ExclusiveCount int `json:"exclusiveCount" datastore:"-"`
+ ID int64 `json:"id,string"`
+ Name string `json:"name"`
+ UpdatedBy string `json:"updatedBy"`
+ Expires time.Time `json:"expires"`
+ Query string `json:"query"`
+ Note string `json:"note"`
}
// ToQuery makes a slice of url.Values from the given slice of IngoreRules.
@@ -79,102 +71,6 @@
}
}
-// MemIgnoreStore is an in-memory implementation of IgnoreStore.
-type MemIgnoreStore struct {
- rules []*IgnoreRule
- mutex sync.Mutex
- nextId int64
- revision int64
-}
-
-func NewMemIgnoreStore() IgnoreStore {
- return &MemIgnoreStore{
- rules: []*IgnoreRule{},
- }
-}
-
-func (m *MemIgnoreStore) inc() {
- m.revision += 1
-}
-
-// Create, see IgnoreStore interface.
-func (m *MemIgnoreStore) Create(rule *IgnoreRule) error {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- rule.ID = m.nextId
- m.nextId++
- m.rules = append(m.rules, rule)
- m.inc()
- return nil
-}
-
-// List, see IgnoreStore interface.
-func (m *MemIgnoreStore) List(addCounts bool) ([]*IgnoreRule, error) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- m.expire()
- result := make([]*IgnoreRule, len(m.rules))
- copy(result, m.rules)
- return result, nil
-}
-
-// Update, see IgnoreStore interface.
-func (m *MemIgnoreStore) Update(id int64, updated *IgnoreRule) error {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- for i := range m.rules {
- if updated.ID == id {
- m.rules[i] = updated
- m.inc()
- return nil
- }
- }
-
- return fmt.Errorf("Did not find an IgnoreRule with id: %d", id)
-}
-
-// Delete, see IgnoreStore interface.
-func (m *MemIgnoreStore) Delete(id int64) (int, error) {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- for idx, rule := range m.rules {
- if rule.ID == id {
- m.rules = append(m.rules[:idx], m.rules[idx+1:]...)
- m.inc()
- return 1, nil
- }
- }
-
- return 0, nil
-}
-
-func (m *MemIgnoreStore) Revision() int64 {
- m.mutex.Lock()
- defer m.mutex.Unlock()
-
- return m.revision
-}
-
-func (m *MemIgnoreStore) expire() {
- newrules := make([]*IgnoreRule, 0, len(m.rules))
- now := time.Now()
- for _, rule := range m.rules {
- if rule.Expires.After(now) {
- newrules = append(newrules, rule)
- }
- }
- m.rules = newrules
-}
-
-// BuildRuleMatcher, see IgnoreStore interface.
-func (m *MemIgnoreStore) BuildRuleMatcher() (RuleMatcher, error) {
- return buildRuleMatcher(m)
-}
-
// TODO(stephana): Factor out QueryRule into the shared library and consolidate
// with the Matches function in the ./go/tiling package.
diff --git a/golden/go/ignore/ignorestore_test.go b/golden/go/ignore/ignorestore_test.go
index eee52c3..6b6117f 100644
--- a/golden/go/ignore/ignorestore_test.go
+++ b/golden/go/ignore/ignorestore_test.go
@@ -6,138 +6,9 @@
"time"
assert "github.com/stretchr/testify/require"
- "go.skia.org/infra/go/ds"
- ds_testutil "go.skia.org/infra/go/ds/testutil"
"go.skia.org/infra/go/testutils/unittest"
)
-func TestTestMemIgnoreStore(t *testing.T) {
- unittest.SmallTest(t)
- memStore := NewMemIgnoreStore()
- testIgnoreStore(t, memStore)
-}
-
-func TestCloudIgnoreStore(t *testing.T) {
- unittest.LargeTest(t)
-
- // Run to the locally running emulator.
- cleanup := ds_testutil.InitDatastore(t,
- ds.IGNORE_RULE,
- ds.HELPER_RECENT_KEYS)
- defer cleanup()
-
- store, err := NewCloudIgnoreStore(ds.DS, nil, nil)
- assert.NoError(t, err)
- testIgnoreStore(t, store)
-}
-
-func testIgnoreStore(t *testing.T, store IgnoreStore) {
- // Add a few instances.
- r1 := NewIgnoreRule("jon@example.com", time.Now().Add(time.Hour), "config=gpu", "reason")
- r2 := NewIgnoreRule("jim@example.com", time.Now().Add(time.Minute*10), "config=8888", "No good reason.")
- r3 := NewIgnoreRule("jon@example.com", time.Now().Add(time.Minute*50), "extra=123&extra=abc", "Ignore multiple.")
- r4 := NewIgnoreRule("jon@example.com", time.Now().Add(time.Minute*100), "extra=123&extra=abc&config=8888", "Ignore multiple.")
- assert.Equal(t, int64(0), store.Revision())
- assert.NoError(t, store.Create(r1))
- assert.NoError(t, store.Create(r2))
- assert.NoError(t, store.Create(r3))
- assert.NoError(t, store.Create(r4))
- assert.Equal(t, int64(4), store.Revision())
-
- allRules, err := store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 4, len(allRules))
- assert.Equal(t, int64(4), store.Revision())
-
- // Test the rule matcher
- matcher, err := store.BuildRuleMatcher()
- assert.NoError(t, err)
- found, ok := matcher(map[string]string{"config": "565"})
- assert.False(t, ok)
- assert.Equal(t, []*IgnoreRule{}, found)
- found, ok = matcher(map[string]string{"config": "8888"})
- assert.True(t, ok)
- assert.Equal(t, 1, len(found))
- found, ok = matcher(map[string]string{"extra": "123"})
- assert.True(t, ok)
- assert.Equal(t, 1, len(found))
- found, ok = matcher(map[string]string{"extra": "abc"})
- assert.True(t, ok)
- assert.Equal(t, 1, len(found))
- found, ok = matcher(map[string]string{"extra": "abc", "config": "8888"})
- assert.True(t, ok)
- assert.Equal(t, 3, len(found))
- found, ok = matcher(map[string]string{"extra": "abc", "config": "gpu"})
- assert.True(t, ok)
- assert.Equal(t, 2, len(found))
- assert.Equal(t, int64(4), store.Revision())
-
- // Remove the third and fourth rule
- delCount, err := store.Delete(r3.ID)
- assert.NoError(t, err)
- assert.Equal(t, 1, delCount)
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 3, len(allRules))
-
- delCount, err = store.Delete(r4.ID)
- assert.NoError(t, err)
- assert.Equal(t, 1, delCount)
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 2, len(allRules))
- assert.Equal(t, int64(6), store.Revision())
-
- for _, oneRule := range allRules {
- assert.True(t, (oneRule.ID == r1.ID) || (oneRule.ID == r2.ID))
- }
-
- delCount, err = store.Delete(r1.ID)
- assert.NoError(t, err)
- assert.Equal(t, 1, delCount)
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 1, len(allRules))
- assert.Equal(t, r2.ID, allRules[0].ID)
- assert.Equal(t, int64(7), store.Revision())
-
- // Update a rule.
- updatedRule := *allRules[0]
- updatedRule.Note = "an updated rule"
- err = store.Update(updatedRule.ID, &updatedRule)
- assert.NoError(t, err, "Update should succeed.")
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 1, len(allRules))
- assert.Equal(t, r2.ID, allRules[0].ID)
- assert.Equal(t, "an updated rule", allRules[0].Note)
- assert.Equal(t, int64(8), store.Revision())
-
- // Try to update a non-existent rule.
- updatedRule = *allRules[0]
- err = store.Update(100001, &updatedRule)
- assert.Error(t, err, "Update should fail for a bad id.")
- assert.Equal(t, int64(8), store.Revision())
-
- delCount, err = store.Delete(r2.ID)
- assert.NoError(t, err)
- assert.Equal(t, 1, delCount)
-
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 0, len(allRules))
- assert.Equal(t, int64(9), store.Revision())
-
- // This id doesn't exist, so we shouldn't be able to delete it.
- delCount, err = store.Delete(1000000)
- assert.NoError(t, err)
- assert.Equal(t, delCount, 0)
- allRules, err = store.List(false)
- assert.NoError(t, err)
- assert.Equal(t, 0, len(allRules))
- assert.Equal(t, int64(9), store.Revision())
-}
-
func TestToQuery(t *testing.T) {
unittest.SmallTest(t)
queries, err := ToQuery([]*IgnoreRule{})
diff --git a/golden/go/ignore/mem_ignorestore/mem_ignorestore.go b/golden/go/ignore/mem_ignorestore/mem_ignorestore.go
new file mode 100644
index 0000000..0cb2ef7
--- /dev/null
+++ b/golden/go/ignore/mem_ignorestore/mem_ignorestore.go
@@ -0,0 +1,105 @@
+package mem_ignorestore
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "go.skia.org/infra/golden/go/ignore"
+)
+
+// MemIgnoreStore is an in-memory implementation of IgnoreStore.
+type MemIgnoreStore struct {
+ rules []*ignore.IgnoreRule
+ mutex sync.Mutex
+ nextId int64
+ revision int64
+}
+
+func New() *MemIgnoreStore {
+ return &MemIgnoreStore{
+ rules: []*ignore.IgnoreRule{},
+ }
+}
+
+func (m *MemIgnoreStore) inc() {
+ m.revision += 1
+}
+
+// Create, see IgnoreStore interface.
+func (m *MemIgnoreStore) Create(rule *ignore.IgnoreRule) error {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ rule.ID = m.nextId
+ m.nextId++
+ m.rules = append(m.rules, rule)
+ m.inc()
+ return nil
+}
+
+// List, see IgnoreStore interface.
+func (m *MemIgnoreStore) List() ([]*ignore.IgnoreRule, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ m.expire()
+ result := make([]*ignore.IgnoreRule, len(m.rules))
+ copy(result, m.rules)
+ return result, nil
+}
+
+// Update, see IgnoreStore interface.
+func (m *MemIgnoreStore) Update(id int64, updated *ignore.IgnoreRule) error {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ for i := range m.rules {
+ if updated.ID == id {
+ m.rules[i] = updated
+ m.inc()
+ return nil
+ }
+ }
+
+ return fmt.Errorf("Did not find an IgnoreRule with id: %d", id)
+}
+
+// Delete, see IgnoreStore interface.
+func (m *MemIgnoreStore) Delete(id int64) (int, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ for idx, rule := range m.rules {
+ if rule.ID == id {
+ m.rules = append(m.rules[:idx], m.rules[idx+1:]...)
+ m.inc()
+ return 1, nil
+ }
+ }
+
+ return 0, nil
+}
+
+func (m *MemIgnoreStore) Revision() int64 {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ return m.revision
+}
+
+func (m *MemIgnoreStore) expire() {
+ newrules := make([]*ignore.IgnoreRule, 0, len(m.rules))
+ now := time.Now()
+ for _, rule := range m.rules {
+ if rule.Expires.After(now) {
+ newrules = append(newrules, rule)
+ }
+ }
+ m.rules = newrules
+}
+
+// BuildRuleMatcher, see IgnoreStore interface.
+func (m *MemIgnoreStore) BuildRuleMatcher() (ignore.RuleMatcher, error) {
+ return ignore.BuildRuleMatcher(m)
+}
diff --git a/golden/go/ignore/mem_ignorestore/mem_ignorestore_test.go b/golden/go/ignore/mem_ignorestore/mem_ignorestore_test.go
new file mode 100644
index 0000000..06a7fb8
--- /dev/null
+++ b/golden/go/ignore/mem_ignorestore/mem_ignorestore_test.go
@@ -0,0 +1,14 @@
+package mem_ignorestore
+
+import (
+ "testing"
+
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/ignore/testutils"
+)
+
+func TestTestMemIgnoreStore(t *testing.T) {
+ unittest.SmallTest(t)
+ memStore := New()
+ testutils.IgnoreStoreAll(t, memStore)
+}
diff --git a/golden/go/ignore/monitor.go b/golden/go/ignore/monitor.go
index a7eb48a..89dd749 100644
--- a/golden/go/ignore/monitor.go
+++ b/golden/go/ignore/monitor.go
@@ -9,7 +9,7 @@
)
func oneStep(store IgnoreStore, metric metrics2.Int64Metric) error {
- list, err := store.List(false)
+ list, err := store.List()
if err != nil {
return err
}
diff --git a/golden/go/ignore/testutils/common.go b/golden/go/ignore/testutils/common.go
new file mode 100644
index 0000000..cf0e25c
--- /dev/null
+++ b/golden/go/ignore/testutils/common.go
@@ -0,0 +1,116 @@
+package testutils
+
+import (
+ "time"
+
+ assert "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/sktest"
+ "go.skia.org/infra/golden/go/ignore"
+)
+
+func IgnoreStoreAll(t sktest.TestingT, store ignore.IgnoreStore) {
+ // Add a few instances.
+ r1 := ignore.NewIgnoreRule("jon@example.com", time.Now().Add(time.Hour), "config=gpu", "reason")
+ r2 := ignore.NewIgnoreRule("jim@example.com", time.Now().Add(time.Minute*10), "config=8888", "No good reason.")
+ r3 := ignore.NewIgnoreRule("jon@example.com", time.Now().Add(time.Minute*50), "extra=123&extra=abc", "Ignore multiple.")
+ r4 := ignore.NewIgnoreRule("jon@example.com", time.Now().Add(time.Minute*100), "extra=123&extra=abc&config=8888", "Ignore multiple.")
+ assert.Equal(t, int64(0), store.Revision())
+ assert.NoError(t, store.Create(r1))
+ assert.NoError(t, store.Create(r2))
+ assert.NoError(t, store.Create(r3))
+ assert.NoError(t, store.Create(r4))
+ assert.Equal(t, int64(4), store.Revision())
+
+ allRules, err := store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 4, len(allRules))
+ assert.Equal(t, int64(4), store.Revision())
+
+ // Test the rule matcher
+ matcher, err := store.BuildRuleMatcher()
+ assert.NoError(t, err)
+ found, ok := matcher(map[string]string{"config": "565"})
+ assert.False(t, ok)
+ assert.Equal(t, []*ignore.IgnoreRule{}, found)
+ found, ok = matcher(map[string]string{"config": "8888"})
+ assert.True(t, ok)
+ assert.Equal(t, 1, len(found))
+ found, ok = matcher(map[string]string{"extra": "123"})
+ assert.True(t, ok)
+ assert.Equal(t, 1, len(found))
+ found, ok = matcher(map[string]string{"extra": "abc"})
+ assert.True(t, ok)
+ assert.Equal(t, 1, len(found))
+ found, ok = matcher(map[string]string{"extra": "abc", "config": "8888"})
+ assert.True(t, ok)
+ assert.Equal(t, 3, len(found))
+ found, ok = matcher(map[string]string{"extra": "abc", "config": "gpu"})
+ assert.True(t, ok)
+ assert.Equal(t, 2, len(found))
+ assert.Equal(t, int64(4), store.Revision())
+
+ // Remove the third and fourth rule
+ delCount, err := store.Delete(r3.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, delCount)
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 3, len(allRules))
+
+ delCount, err = store.Delete(r4.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, delCount)
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(allRules))
+ assert.Equal(t, int64(6), store.Revision())
+
+ for _, oneRule := range allRules {
+ assert.True(t, (oneRule.ID == r1.ID) || (oneRule.ID == r2.ID))
+ }
+
+ delCount, err = store.Delete(r1.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, delCount)
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(allRules))
+ assert.Equal(t, r2.ID, allRules[0].ID)
+ assert.Equal(t, int64(7), store.Revision())
+
+ // Update a rule.
+ updatedRule := *allRules[0]
+ updatedRule.Note = "an updated rule"
+ err = store.Update(updatedRule.ID, &updatedRule)
+ assert.NoError(t, err, "Update should succeed.")
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(allRules))
+ assert.Equal(t, r2.ID, allRules[0].ID)
+ assert.Equal(t, "an updated rule", allRules[0].Note)
+ assert.Equal(t, int64(8), store.Revision())
+
+ // Try to update a non-existent rule.
+ updatedRule = *allRules[0]
+ err = store.Update(100001, &updatedRule)
+ assert.Error(t, err, "Update should fail for a bad id.")
+ assert.Equal(t, int64(8), store.Revision())
+
+ delCount, err = store.Delete(r2.ID)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, delCount)
+
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 0, len(allRules))
+ assert.Equal(t, int64(9), store.Revision())
+
+ // This id doesn't exist, so we shouldn't be able to delete it.
+ delCount, err = store.Delete(1000000)
+ assert.NoError(t, err)
+ assert.Equal(t, delCount, 0)
+ allRules, err = store.List()
+ assert.NoError(t, err)
+ assert.Equal(t, 0, len(allRules))
+ assert.Equal(t, int64(9), store.Revision())
+}
diff --git a/golden/go/serialize/serialize.go b/golden/go/serialize/serialize.go
index c28eeb6..80258ad 100644
--- a/golden/go/serialize/serialize.go
+++ b/golden/go/serialize/serialize.go
@@ -61,14 +61,16 @@
// problems like https://crbug.com/skia/9070
// TODO(kjlubick): regenerate/replace test data and remove this struct
type legacyIgnoreRule struct {
- ID int64 `json:"id"`
- Name string `json:"name"`
- UpdatedBy string `json:"updatedBy"`
- Expires time.Time `json:"expires"`
- Query string `json:"query"`
- Note string `json:"note"`
- Count int `json:"count" datastore:"-"`
- ExclusiveCount int `json:"exclusiveCount" datastore:"-"`
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ UpdatedBy string `json:"updatedBy"`
+ Expires time.Time `json:"expires"`
+ Query string `json:"query"`
+ Note string `json:"note"`
+ // Count and ExclusiveCount were removed to simplify the ignorestore (and remove
+ // a noxious cyclical dependency.)
+ Count int `json:"count" datastore:"-"`
+ ExclusiveCount int `json:"exclusiveCount" datastore:"-"`
}
// DeserializeSample returns a new instance of Sample from the given reader. It
@@ -116,14 +118,12 @@
ret.IgnoreRules = make([]*ignore.IgnoreRule, 0, len(legacyRules))
for _, ir := range legacyRules {
ret.IgnoreRules = append(ret.IgnoreRules, &ignore.IgnoreRule{
- ID: ir.ID,
- Name: ir.Name,
- UpdatedBy: ir.UpdatedBy,
- Expires: ir.Expires,
- Query: ir.Query,
- Note: ir.Note,
- Count: ir.Count,
- ExclusiveCount: ir.ExclusiveCount,
+ ID: ir.ID,
+ Name: ir.Name,
+ UpdatedBy: ir.UpdatedBy,
+ Expires: ir.Expires,
+ Query: ir.Query,
+ Note: ir.Note,
})
}
diff --git a/golden/go/storage/storage.go b/golden/go/storage/storage.go
index 9d56ca9..6eeddf5 100644
--- a/golden/go/storage/storage.go
+++ b/golden/go/storage/storage.go
@@ -234,7 +234,7 @@
// that match the ignore rules in the given ignore store. It also returns the
// ignore rules for later matching.
func FilterIgnored(inputTile *tiling.Tile, ignoreStore ignore.IgnoreStore) (*tiling.Tile, paramtools.ParamMatcher, error) {
- ignores, err := ignoreStore.List(false)
+ ignores, err := ignoreStore.List()
if err != nil {
return nil, nil, fmt.Errorf("Failed to get ignores to filter tile: %s", err)
}
diff --git a/golden/go/summary/summary_test.go b/golden/go/summary/summary_test.go
index 1ef9692..bd22a40 100644
--- a/golden/go/summary/summary_test.go
+++ b/golden/go/summary/summary_test.go
@@ -11,6 +11,7 @@
"go.skia.org/infra/golden/go/blame"
"go.skia.org/infra/golden/go/digest_counter"
"go.skia.org/infra/golden/go/ignore"
+ "go.skia.org/infra/golden/go/ignore/mem_ignorestore"
"go.skia.org/infra/golden/go/mocks"
"go.skia.org/infra/golden/go/storage"
"go.skia.org/infra/golden/go/summary"
@@ -164,7 +165,7 @@
storages := &storage.Storage{
ExpectationsStore: mes,
- IgnoreStore: ignore.NewMemIgnoreStore(),
+ IgnoreStore: mem_ignorestore.New(),
MasterTileBuilder: mocks.NewMockTileBuilderFromTile(t, tile),
NCommits: 50,
}
@@ -178,6 +179,8 @@
Query: "config=565",
}))
+ // TODO(kjlubick): FilterIgnored should be tested elsewhere. This would let us
+ // remove the dependency on MasterTileBuilder and IgnoreStore.
tileWithIgnoreApplied, _, err := storage.FilterIgnored(tile, storages.IgnoreStore)
assert.NoError(t, err)
diff --git a/golden/go/web/web.go b/golden/go/web/web.go
index 3d8bb0c..6cf8077 100644
--- a/golden/go/web/web.go
+++ b/golden/go/web/web.go
@@ -414,7 +414,9 @@
defer metrics2.FuncTimer().Stop()
w.Header().Set("Content-Type", "application/json")
- ignores, err := wh.Storages.IgnoreStore.List(true)
+ // TODO(kjlubick): these ignore structs used to have counts of how often they were applied
+ // in the file - Fix that after the Storages refactoring.
+ ignores, err := wh.Storages.IgnoreStore.List()
if err != nil {
httputils.ReportError(w, r, err, "Failed to retrieve ignore rules, there may be none.")
return