cmd/coordinator/internal/dashboard: filter ErrNoSuchEntity
The AppEngine dashboard filters out datastore.ErrNoSuchEntity, which
happens when we fetch a commit that has no build information yet. This
change ports that functionality to the coordinator dashboard.
Updates golang/go#34744
Change-Id: I38f6b2e1a1805fb24b9e387a5737ff8c2057b625
Reviewed-on: https://go-review.googlesource.com/c/build/+/222197
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/cmd/coordinator/internal/dashboard/datastore.go b/cmd/coordinator/internal/dashboard/datastore.go
index 2658d94..0d9305c 100644
--- a/cmd/coordinator/internal/dashboard/datastore.go
+++ b/cmd/coordinator/internal/dashboard/datastore.go
@@ -9,6 +9,7 @@
import (
"context"
+ "errors"
"log"
"cloud.google.com/go/datastore"
@@ -25,7 +26,8 @@
keys = append(keys, key)
}
out := make([]*Commit, len(keys))
- if err := cl.GetMulti(ctx, keys, out); err != nil {
+ // datastore.ErrNoSuchEntity is returned when we ask for a commit that we do not yet have test data.
+ if err := cl.GetMulti(ctx, keys, out); err != nil && filterMultiError(err, ignoreNoSuchEntity) != nil {
log.Printf("getResults: error fetching %d results: %v", len(keys), err)
return
}
@@ -42,3 +44,45 @@
}
return
}
+
+type ignoreFunc func(err error) error
+
+// ignoreNoSuchEntity ignores datastore.ErrNoSuchEntity, which is returned when
+// we ask for a commit that we do not yet have test data.
+func ignoreNoSuchEntity(err error) error {
+ if !errors.Is(err, datastore.ErrNoSuchEntity) {
+ return err
+ }
+ return nil
+}
+
+// filterMultiError loops over datastore.MultiError, skipping errors ignored by
+// the specified ignoreFuncs. Any unfiltered errors will be returned as a
+// datastore.MultiError error. If no errors are left, nil will be returned.
+// Errors that are not datastore.MultiError will be returned as-is.
+func filterMultiError(err error, ignores ...ignoreFunc) error {
+ if err == nil {
+ return nil
+ }
+ me := datastore.MultiError{}
+ if ok := errors.As(err, &me); !ok {
+ return err
+ }
+ ret := datastore.MultiError{}
+ for _, err := range me {
+ var skip bool
+ for _, ignore := range ignores {
+ if err := ignore(err); err == nil {
+ skip = true
+ break
+ }
+ }
+ if !skip {
+ ret = append(ret, err)
+ }
+ }
+ if len(ret) > 0 {
+ return ret
+ }
+ return nil
+}
diff --git a/cmd/coordinator/internal/dashboard/datastore_test.go b/cmd/coordinator/internal/dashboard/datastore_test.go
new file mode 100644
index 0000000..0a5507c
--- /dev/null
+++ b/cmd/coordinator/internal/dashboard/datastore_test.go
@@ -0,0 +1,91 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+// +build linux darwin
+
+package dashboard
+
+import (
+ "errors"
+ "testing"
+
+ "cloud.google.com/go/datastore"
+)
+
+var testError = errors.New("this is a test error for cmd/coordinator")
+
+func ignoreTestError(err error) error {
+ if !errors.Is(err, testError) {
+ return err
+ }
+ return nil
+}
+
+func ignoreNothing(err error) error {
+ return err
+}
+
+func TestFilterMultiError(t *testing.T) {
+ cases := []struct {
+ desc string
+ err error
+ ignores []ignoreFunc
+ wantErr bool
+ }{
+ {
+ desc: "single ignored error",
+ err: datastore.MultiError{testError},
+ ignores: []ignoreFunc{ignoreTestError},
+ },
+ {
+ desc: "multiple ignored errors",
+ err: datastore.MultiError{testError, testError},
+ ignores: []ignoreFunc{ignoreTestError},
+ },
+ {
+ desc: "non-ignored error",
+ err: datastore.MultiError{testError, errors.New("this should fail")},
+ ignores: []ignoreFunc{ignoreTestError},
+ wantErr: true,
+ },
+ {
+ desc: "nil error",
+ ignores: []ignoreFunc{ignoreTestError},
+ },
+ {
+ desc: "non-multistore error",
+ err: errors.New("this should fail"),
+ ignores: []ignoreFunc{ignoreTestError},
+ wantErr: true,
+ },
+ {
+ desc: "no ignoreFuncs",
+ err: errors.New("this should fail"),
+ ignores: []ignoreFunc{},
+ wantErr: true,
+ },
+ {
+ desc: "if any ignoreFunc ignores, error is ignored.",
+ err: datastore.MultiError{testError, testError},
+ ignores: []ignoreFunc{ignoreNothing, ignoreTestError},
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.desc, func(t *testing.T) {
+ if err := filterMultiError(c.err, c.ignores...); (err != nil) != c.wantErr {
+ t.Errorf("filterMultiError(%v, %v) = %v, wantErr = %v", c.err, c.ignores, err, c.wantErr)
+ }
+ })
+ }
+}
+
+func TestIgnoreNoSuchEntity(t *testing.T) {
+ if err := ignoreNoSuchEntity(datastore.ErrNoSuchEntity); err != nil {
+ t.Errorf("ignoreNoSuchEntity(%v) = %v, wanted no error", datastore.ErrNoSuchEntity, err)
+ }
+ if err := ignoreNoSuchEntity(datastore.ErrInvalidKey); err == nil {
+ t.Errorf("ignoreNoSuchEntity(%v) = %v, wanted error", datastore.ErrInvalidKey, err)
+ }
+}