| // Copyright 2017 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. |
| |
| package godata |
| |
| import ( |
| "bytes" |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "sort" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "cloud.google.com/go/compute/metadata" |
| "golang.org/x/build/gerrit" |
| "golang.org/x/build/internal/secret" |
| "golang.org/x/build/maintner" |
| ) |
| |
| func BenchmarkGet(b *testing.B) { |
| b.ReportAllocs() |
| for i := 0; i < b.N; i++ { |
| _, err := Get(context.Background()) |
| if err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| var ( |
| corpusMu sync.Mutex |
| corpusCache *maintner.Corpus |
| ) |
| |
| func getGoData(tb testing.TB) *maintner.Corpus { |
| if testing.Short() { |
| tb.Skip("not running tests requiring large download in short mode") |
| } |
| corpusMu.Lock() |
| defer corpusMu.Unlock() |
| if corpusCache != nil { |
| return corpusCache |
| } |
| var err error |
| corpusCache, err = Get(context.Background()) |
| if err != nil { |
| tb.Fatalf("getting corpus: %v", err) |
| } |
| return corpusCache |
| } |
| |
| func TestCorpusCheck(t *testing.T) { |
| c := getGoData(t) |
| if err := c.Check(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestGerritForeachNonChangeRef(t *testing.T) { |
| c := getGoData(t) |
| c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error { |
| t.Logf("%s:", gp.ServerSlashProject()) |
| gp.ForeachNonChangeRef(func(ref string, hash maintner.GitHash) error { |
| t.Logf(" %s %s", hash, ref) |
| return nil |
| }) |
| return nil |
| }) |
| } |
| |
| // In the past, some Gerrit ref changes came before the git in the log. |
| // This tests that we handle Gerrit meta changes that happen before |
| // the referenced git commit is known. |
| func TestGerritOutOfOrderMetaChanges(t *testing.T) { |
| c := getGoData(t) |
| |
| // Merged: |
| goProj := c.Gerrit().Project("go.googlesource.com", "go") |
| cl := goProj.CL(38634) |
| if cl == nil { |
| t.Fatal("CL 38634 not found") |
| } |
| if g, w := cl.Status, "merged"; g != w { |
| t.Errorf("CL status = %q; want %q", g, w) |
| } |
| |
| // Deleted: |
| gddo := c.Gerrit().Project("go.googlesource.com", "gddo") |
| cl = gddo.CL(37452) |
| if cl == nil { |
| t.Fatal("CL 37452 not found") |
| } |
| t.Logf("Got: %+v", *cl) |
| } |
| |
| func TestGerritSkipPrivateCLs(t *testing.T) { |
| c := getGoData(t) |
| proj := c.Gerrit().Project("go.googlesource.com", "gddo") |
| proj.ForeachOpenCL(func(cl *maintner.GerritCL) error { |
| if cl.Number == 37452 { |
| t.Error("unexpected private CL 37452") |
| } |
| return nil |
| }) |
| } |
| |
| func TestGerritMetaNonNil(t *testing.T) { |
| c := getGoData(t) |
| c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error { |
| var maxCL int32 |
| gp.ForeachCLUnsorted(func(cl *maintner.GerritCL) error { |
| if cl.Meta == nil { |
| t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has nil Meta", gp.ServerSlashProject(), cl.Number) |
| } |
| if len(cl.Metas) == 0 { |
| t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has empty Metas", gp.ServerSlashProject(), cl.Number) |
| } |
| if cl.Commit == nil { |
| t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has nil Commit", gp.ServerSlashProject(), cl.Number) |
| } |
| if cl.Number > maxCL { |
| maxCL = cl.Number |
| } |
| return nil |
| }) |
| gp.ForeachOpenCL(func(cl *maintner.GerritCL) error { |
| if cl.Meta == nil { |
| t.Errorf("%s: ForeachOpenCL-enumerated CL %d has nil Meta", gp.ServerSlashProject(), cl.Number) |
| } |
| if len(cl.Metas) == 0 { |
| t.Errorf("%s: ForeachOpenCL-enumerated CL %d has empty Metas", gp.ServerSlashProject(), cl.Number) |
| } |
| if cl.Commit == nil { |
| t.Errorf("%s: ForeachOpenCL-enumerated CL %d has nil Commit", gp.ServerSlashProject(), cl.Number) |
| } |
| if cl.Number > maxCL { |
| t.Fatalf("%s: ForeachOpenCL-enumerated CL %d higher than max CL %d from ForeachCLUnsorted", gp.ServerSlashProject(), cl.Number, maxCL) |
| } |
| return nil |
| }) |
| |
| // And test that CL won't yield an incomplete one either: |
| for n := int32(0); n <= maxCL; n++ { |
| cl := gp.CL(n) |
| if cl == nil { |
| continue |
| } |
| if cl.Meta == nil { |
| t.Errorf("%s: CL(%d) has nil Meta", gp.ServerSlashProject(), cl.Number) |
| } |
| if len(cl.Metas) == 0 { |
| t.Errorf("%s: CL(%d) has empty Metas", gp.ServerSlashProject(), cl.Number) |
| } |
| if cl.Commit == nil { |
| t.Errorf("%s: CL(%d) has nil Commit", gp.ServerSlashProject(), cl.Number) |
| } |
| } |
| return nil |
| }) |
| } |
| |
| func TestGitAncestor(t *testing.T) { |
| c := getGoData(t) |
| tests := []struct { |
| subject, ancestor string |
| want bool |
| }{ |
| {"3b5637ff2bd5c03479780995e7a35c48222157c1", "0bb0b61d6a85b2a1a33dcbc418089656f2754d32", true}, |
| {"0bb0b61d6a85b2a1a33dcbc418089656f2754d32", "3b5637ff2bd5c03479780995e7a35c48222157c1", false}, |
| |
| {"8f06e217eac10bae4993ca371ade35fecd26270e", "22f1b56dab29d397d2bdbdd603d85e60fb678089", true}, |
| {"22f1b56dab29d397d2bdbdd603d85e60fb678089", "8f06e217eac10bae4993ca371ade35fecd26270e", false}, |
| |
| // Was crashing. Issue 22753. |
| {"3a181dc7bc8fd0c61d6090a85f87c934f1874802", "f65abf6ddc8d1f3d403a9195fd74eaffa022b07f", true}, |
| // The reverse of the above, to try to reproduce the |
| // panic if I got the order backwards: |
| {"f65abf6ddc8d1f3d403a9195fd74eaffa022b07f", "3a181dc7bc8fd0c61d6090a85f87c934f1874802", false}, |
| |
| // Same on both sides: |
| {"0bb0b61d6a85b2a1a33dcbc418089656f2754d32", "0bb0b61d6a85b2a1a33dcbc418089656f2754d32", false}, |
| {"3b5637ff2bd5c03479780995e7a35c48222157c1", "3b5637ff2bd5c03479780995e7a35c48222157c1", false}, |
| } |
| for i, tt := range tests { |
| subject := c.GitCommit(tt.subject) |
| if subject == nil { |
| t.Errorf("%d. missing subject commit %q", i, tt.subject) |
| continue |
| } |
| anc := c.GitCommit(tt.ancestor) |
| if anc == nil { |
| t.Errorf("%d. missing ancestor commit %q", i, tt.ancestor) |
| continue |
| } |
| got := subject.HasAncestor(anc) |
| if got != tt.want { |
| t.Errorf("HasAncestor(%q, %q) = %v; want %v", tt.subject, tt.ancestor, got, tt.want) |
| } |
| } |
| } |
| |
| func BenchmarkGitAncestor(b *testing.B) { |
| c := getGoData(b) |
| subject := c.GitCommit("3b5637ff2bd5c03479780995e7a35c48222157c1") |
| anc := c.GitCommit("0bb0b61d6a85b2a1a33dcbc418089656f2754d32") |
| if subject == nil || anc == nil { |
| b.Fatal("missing commit(s)") |
| } |
| |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| if !subject.HasAncestor(anc) { |
| b.Fatal("wrong answer") |
| } |
| } |
| } |
| |
| // Issue 23007: a Gerrit CL can switch branches. Make sure we handle that. |
| func TestGerritCLChangingBranches(t *testing.T) { |
| c := getGoData(t) |
| |
| tests := []struct { |
| server, project string |
| cl int32 |
| want string |
| }{ |
| // Changed branch in the middle: |
| // (Unsubmitted at the time of this test, so if it changes back, this test |
| // may break.) |
| {"go.googlesource.com", "go", 33776, "master"}, |
| |
| // Submitted to boringcrypto: |
| {"go.googlesource.com", "go", 82138, "dev.boringcrypto"}, |
| // Submitted to master: |
| {"go.googlesource.com", "go", 83578, "master"}, |
| } |
| |
| for _, tt := range tests { |
| cl := c.Gerrit().Project(tt.server, tt.project).CL(tt.cl) |
| if got := cl.Branch(); got != tt.want { |
| t.Errorf("%q, %q, CL %d = branch %q; want %q", tt.server, tt.project, tt.cl, got, tt.want) |
| } |
| } |
| } |
| |
| func TestGerritHashTags(t *testing.T) { |
| c := getGoData(t) |
| cl := c.Gerrit().Project("go.googlesource.com", "go").CL(81778) |
| want := `added "bar, foo" = "bar,foo" |
| removed "bar" = "foo" |
| removed "foo" = "" |
| added "bar, foo" = "bar,foo" |
| removed "bar" = "foo" |
| added "bar" = "bar,foo" |
| added "blarf, quux" removed "foo" = "bar,quux,blarf" |
| removed "bar" = "quux,blarf" |
| ` |
| |
| var log bytes.Buffer |
| for _, meta := range cl.Metas { |
| added, removed, ok := meta.HashtagEdits() |
| if ok { |
| if added != "" { |
| fmt.Fprintf(&log, "added %q ", added) |
| } |
| if removed != "" { |
| fmt.Fprintf(&log, "removed %q ", removed) |
| } |
| fmt.Fprintf(&log, "= %q\n", meta.Hashtags()) |
| } |
| } |
| got := log.String() |
| if !strings.HasPrefix(got, want) { |
| t.Errorf("got:\n%s\n\nwant prefix:\n%s", got, want) |
| } |
| } |
| |
| // getSecret retrieves a secret by name from the secret manager service. |
| func getSecret(name string) (string, error) { |
| sc, err := secret.NewClient() |
| if err != nil { |
| return "", err |
| } |
| defer sc.Close() |
| |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| defer cancel() |
| |
| return sc.Retrieve(ctx, name) |
| } |
| |
| func getGerritAuth() (username string, password string, err error) { |
| var slurp string |
| if metadata.OnGCE() { |
| slurp, _ = getSecret(secret.NameGobotPassword) |
| } |
| if slurp == "" { |
| var ok bool |
| slurp, ok = os.LookupEnv("TEST_GERRIT_AUTH") |
| if !ok { |
| return "", "", errors.New("environment variable TEST_GERRIT_AUTH is not set") |
| } |
| } |
| f := strings.SplitN(strings.TrimSpace(slurp), ":", 2) |
| if len(f) == 1 { |
| // assume the whole thing is the token |
| return "git-gobot.golang.org", f[0], nil |
| } |
| if len(f) != 2 || f[0] == "" || f[1] == "" { |
| return "", "", fmt.Errorf("Expected Gerrit token %q to be of form <git-email>:<token>", slurp) |
| } |
| return f[0], f[1], nil |
| } |
| |
| // Hit the Gerrit API and compare its computation of CLs' hashtags against what maintner thinks. |
| // Off by default unless $TEST_GERRIT_AUTH is defined with "user:token", or we're running in the |
| // prod project. |
| func TestGerritHashtags(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping in short mode") |
| } |
| c := getGoData(t) |
| user, pass, err := getGerritAuth() |
| if err != nil { |
| t.Skipf("no Gerrit auth defined, skipping: %v", err) |
| } |
| gc := gerrit.NewClient("https://go-review.googlesource.com", gerrit.BasicAuth(user, pass)) |
| ctx := context.Background() |
| more := true |
| n := 0 |
| for more { |
| // We search Gerrit for "hashtag", which seems to also |
| // search auto-generated gerrit meta (notedb) texts, |
| // so this has the effect of searching for all Gerrit |
| // changes that have ever had hashtags added or |
| // removed: |
| cis, err := gc.QueryChanges(ctx, "hashtag", gerrit.QueryChangesOpt{ |
| Start: n, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| for _, ci := range cis { |
| n++ |
| cl := c.Gerrit().Project("go.googlesource.com", ci.Project).CL(int32(ci.ChangeNumber)) |
| if cl == nil { |
| t.Logf("Ignoring not-in-maintner %s/%v", ci.Project, ci.ChangeNumber) |
| continue |
| } |
| sort.Strings(ci.Hashtags) |
| want := strings.Join(ci.Hashtags, ", ") |
| got := canonicalTagList(string(cl.Meta.Hashtags())) |
| if got != want { |
| t.Errorf("ci: https://golang.org/cl/%d (%s) -- maintner = %q; want gerrit value %q", ci.ChangeNumber, ci.Project, got, want) |
| } |
| more = ci.MoreChanges |
| } |
| } |
| t.Logf("N = %v", n) |
| } |
| |
| func canonicalTagList(s string) string { |
| var sl []string |
| for _, v := range strings.Split(s, ",") { |
| sl = append(sl, strings.TrimSpace(v)) |
| } |
| sort.Strings(sl) |
| return strings.Join(sl, ", ") |
| } |