golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/godata/godata_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package godata
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"os"
    13  	"sort"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	"cloud.google.com/go/compute/metadata"
    20  	"golang.org/x/build/gerrit"
    21  	"golang.org/x/build/internal/secret"
    22  	"golang.org/x/build/maintner"
    23  )
    24  
    25  func BenchmarkGet(b *testing.B) {
    26  	b.ReportAllocs()
    27  	for i := 0; i < b.N; i++ {
    28  		_, err := Get(context.Background())
    29  		if err != nil {
    30  			b.Fatal(err)
    31  		}
    32  	}
    33  }
    34  
    35  var (
    36  	corpusMu    sync.Mutex
    37  	corpusCache *maintner.Corpus
    38  )
    39  
    40  func getGoData(tb testing.TB) *maintner.Corpus {
    41  	if testing.Short() {
    42  		tb.Skip("skipping test requiring large download in short mode")
    43  	}
    44  	corpusMu.Lock()
    45  	defer corpusMu.Unlock()
    46  	if corpusCache != nil {
    47  		return corpusCache
    48  	}
    49  	var err error
    50  	corpusCache, err = Get(context.Background())
    51  	if err != nil {
    52  		tb.Fatalf("getting corpus: %v", err)
    53  	}
    54  	return corpusCache
    55  }
    56  
    57  func TestCorpusCheck(t *testing.T) {
    58  	c := getGoData(t)
    59  	if err := c.Check(); err != nil {
    60  		t.Fatal(err)
    61  	}
    62  }
    63  
    64  func TestGerritForeachNonChangeRef(t *testing.T) {
    65  	c := getGoData(t)
    66  	c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error {
    67  		t.Logf("%s:", gp.ServerSlashProject())
    68  		gp.ForeachNonChangeRef(func(ref string, hash maintner.GitHash) error {
    69  			t.Logf("  %s %s", hash, ref)
    70  			return nil
    71  		})
    72  		return nil
    73  	})
    74  }
    75  
    76  // In the past, some Gerrit ref changes came before the git in the log.
    77  // This tests that we handle Gerrit meta changes that happen before
    78  // the referenced git commit is known.
    79  func TestGerritOutOfOrderMetaChanges(t *testing.T) {
    80  	c := getGoData(t)
    81  
    82  	// Merged:
    83  	goProj := c.Gerrit().Project("go.googlesource.com", "go")
    84  	cl := goProj.CL(38634)
    85  	if cl == nil {
    86  		t.Fatal("CL 38634 not found")
    87  	}
    88  	if g, w := cl.Status, "merged"; g != w {
    89  		t.Errorf("CL status = %q; want %q", g, w)
    90  	}
    91  
    92  	// Deleted:
    93  	gddo := c.Gerrit().Project("go.googlesource.com", "gddo")
    94  	cl = gddo.CL(37452)
    95  	if cl == nil {
    96  		t.Fatal("CL 37452 not found")
    97  	}
    98  	t.Logf("Got: %+v", *cl)
    99  }
   100  
   101  func TestGerritSkipPrivateCLs(t *testing.T) {
   102  	c := getGoData(t)
   103  	proj := c.Gerrit().Project("go.googlesource.com", "gddo")
   104  	proj.ForeachOpenCL(func(cl *maintner.GerritCL) error {
   105  		if cl.Number == 37452 {
   106  			t.Error("unexpected private CL 37452")
   107  		}
   108  		return nil
   109  	})
   110  }
   111  
   112  func TestGerritMetaNonNil(t *testing.T) {
   113  	c := getGoData(t)
   114  	c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error {
   115  		var maxCL int32
   116  		gp.ForeachCLUnsorted(func(cl *maintner.GerritCL) error {
   117  			if cl.Meta == nil {
   118  				t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has nil Meta", gp.ServerSlashProject(), cl.Number)
   119  			}
   120  			if len(cl.Metas) == 0 {
   121  				t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has empty Metas", gp.ServerSlashProject(), cl.Number)
   122  			}
   123  			if cl.Commit == nil {
   124  				t.Errorf("%s: ForeachCLUnsorted-enumerated CL %d has nil Commit", gp.ServerSlashProject(), cl.Number)
   125  			}
   126  			if cl.Number > maxCL {
   127  				maxCL = cl.Number
   128  			}
   129  			return nil
   130  		})
   131  		gp.ForeachOpenCL(func(cl *maintner.GerritCL) error {
   132  			if cl.Meta == nil {
   133  				t.Errorf("%s: ForeachOpenCL-enumerated CL %d has nil Meta", gp.ServerSlashProject(), cl.Number)
   134  			}
   135  			if len(cl.Metas) == 0 {
   136  				t.Errorf("%s: ForeachOpenCL-enumerated CL %d has empty Metas", gp.ServerSlashProject(), cl.Number)
   137  			}
   138  			if cl.Commit == nil {
   139  				t.Errorf("%s: ForeachOpenCL-enumerated CL %d has nil Commit", gp.ServerSlashProject(), cl.Number)
   140  			}
   141  			if cl.Number > maxCL {
   142  				t.Fatalf("%s: ForeachOpenCL-enumerated CL %d higher than max CL %d from ForeachCLUnsorted", gp.ServerSlashProject(), cl.Number, maxCL)
   143  			}
   144  			return nil
   145  		})
   146  
   147  		// And test that CL won't yield an incomplete one either:
   148  		for n := int32(0); n <= maxCL; n++ {
   149  			cl := gp.CL(n)
   150  			if cl == nil {
   151  				continue
   152  			}
   153  			if cl.Meta == nil {
   154  				t.Errorf("%s: CL(%d) has nil Meta", gp.ServerSlashProject(), cl.Number)
   155  			}
   156  			if len(cl.Metas) == 0 {
   157  				t.Errorf("%s: CL(%d) has empty Metas", gp.ServerSlashProject(), cl.Number)
   158  			}
   159  			if cl.Commit == nil {
   160  				t.Errorf("%s: CL(%d) has nil Commit", gp.ServerSlashProject(), cl.Number)
   161  			}
   162  		}
   163  		return nil
   164  	})
   165  }
   166  
   167  func TestGitAncestor(t *testing.T) {
   168  	c := getGoData(t)
   169  	tests := []struct {
   170  		subject, ancestor string
   171  		want              bool
   172  	}{
   173  		{"3b5637ff2bd5c03479780995e7a35c48222157c1", "0bb0b61d6a85b2a1a33dcbc418089656f2754d32", true},
   174  		{"0bb0b61d6a85b2a1a33dcbc418089656f2754d32", "3b5637ff2bd5c03479780995e7a35c48222157c1", false},
   175  
   176  		{"8f06e217eac10bae4993ca371ade35fecd26270e", "22f1b56dab29d397d2bdbdd603d85e60fb678089", true},
   177  		{"22f1b56dab29d397d2bdbdd603d85e60fb678089", "8f06e217eac10bae4993ca371ade35fecd26270e", false},
   178  
   179  		// Was crashing. Issue 22753.
   180  		{"3a181dc7bc8fd0c61d6090a85f87c934f1874802", "f65abf6ddc8d1f3d403a9195fd74eaffa022b07f", true},
   181  		// The reverse of the above, to try to reproduce the
   182  		// panic if I got the order backwards:
   183  		{"f65abf6ddc8d1f3d403a9195fd74eaffa022b07f", "3a181dc7bc8fd0c61d6090a85f87c934f1874802", false},
   184  
   185  		// Same on both sides:
   186  		{"0bb0b61d6a85b2a1a33dcbc418089656f2754d32", "0bb0b61d6a85b2a1a33dcbc418089656f2754d32", false},
   187  		{"3b5637ff2bd5c03479780995e7a35c48222157c1", "3b5637ff2bd5c03479780995e7a35c48222157c1", false},
   188  	}
   189  	for i, tt := range tests {
   190  		subject := c.GitCommit(tt.subject)
   191  		if subject == nil {
   192  			t.Errorf("%d. missing subject commit %q", i, tt.subject)
   193  			continue
   194  		}
   195  		anc := c.GitCommit(tt.ancestor)
   196  		if anc == nil {
   197  			t.Errorf("%d. missing ancestor commit %q", i, tt.ancestor)
   198  			continue
   199  		}
   200  		got := subject.HasAncestor(anc)
   201  		if got != tt.want {
   202  			t.Errorf("HasAncestor(%q, %q) = %v; want %v", tt.subject, tt.ancestor, got, tt.want)
   203  		}
   204  	}
   205  }
   206  
   207  func BenchmarkGitAncestor(b *testing.B) {
   208  	c := getGoData(b)
   209  	subject := c.GitCommit("3b5637ff2bd5c03479780995e7a35c48222157c1")
   210  	anc := c.GitCommit("0bb0b61d6a85b2a1a33dcbc418089656f2754d32")
   211  	if subject == nil || anc == nil {
   212  		b.Fatal("missing commit(s)")
   213  	}
   214  
   215  	b.ResetTimer()
   216  	for i := 0; i < b.N; i++ {
   217  		if !subject.HasAncestor(anc) {
   218  			b.Fatal("wrong answer")
   219  		}
   220  	}
   221  }
   222  
   223  // Issue 23007: a Gerrit CL can switch branches. Make sure we handle that.
   224  func TestGerritCLChangingBranches(t *testing.T) {
   225  	c := getGoData(t)
   226  
   227  	tests := []struct {
   228  		server, project string
   229  		cl              int32
   230  		want            string
   231  	}{
   232  		// Changed branch in the middle:
   233  		// (Unsubmitted at the time of this test, so if it changes back, this test
   234  		// may break.)
   235  		{"go.googlesource.com", "go", 33776, "master"},
   236  
   237  		// Submitted to boringcrypto:
   238  		{"go.googlesource.com", "go", 82138, "dev.boringcrypto"},
   239  		// Submitted to master:
   240  		{"go.googlesource.com", "go", 83578, "master"},
   241  	}
   242  
   243  	for _, tt := range tests {
   244  		cl := c.Gerrit().Project(tt.server, tt.project).CL(tt.cl)
   245  		if got := cl.Branch(); got != tt.want {
   246  			t.Errorf("%q, %q, CL %d = branch %q; want %q", tt.server, tt.project, tt.cl, got, tt.want)
   247  		}
   248  	}
   249  }
   250  
   251  func TestGerritHashTags(t *testing.T) {
   252  	c := getGoData(t)
   253  	cl := c.Gerrit().Project("go.googlesource.com", "go").CL(81778)
   254  	want := `added "bar, foo" = "bar,foo"
   255  removed "bar" = "foo"
   256  removed "foo" = ""
   257  added "bar, foo" = "bar,foo"
   258  removed "bar" = "foo"
   259  added "bar" = "bar,foo"
   260  added "blarf, quux" removed "foo" = "bar,quux,blarf"
   261  removed "bar" = "quux,blarf"
   262  `
   263  
   264  	var log bytes.Buffer
   265  	for _, meta := range cl.Metas {
   266  		added, removed, ok := meta.HashtagEdits()
   267  		if ok {
   268  			if added != "" {
   269  				fmt.Fprintf(&log, "added %q ", added)
   270  			}
   271  			if removed != "" {
   272  				fmt.Fprintf(&log, "removed %q ", removed)
   273  			}
   274  			fmt.Fprintf(&log, "= %q\n", meta.Hashtags())
   275  		}
   276  	}
   277  	got := log.String()
   278  	if !strings.HasPrefix(got, want) {
   279  		t.Errorf("got:\n%s\n\nwant prefix:\n%s", got, want)
   280  	}
   281  }
   282  
   283  // getSecret retrieves a secret by name from the secret manager service.
   284  func getSecret(name string) (string, error) {
   285  	sc, err := secret.NewClient()
   286  	if err != nil {
   287  		return "", err
   288  	}
   289  	defer sc.Close()
   290  
   291  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   292  	defer cancel()
   293  
   294  	return sc.Retrieve(ctx, name)
   295  }
   296  
   297  func getGerritAuth() (username string, password string, err error) {
   298  	var slurp string
   299  	if metadata.OnGCE() {
   300  		slurp, _ = getSecret(secret.NameGobotPassword)
   301  	}
   302  	if slurp == "" {
   303  		var ok bool
   304  		slurp, ok = os.LookupEnv("TEST_GERRIT_AUTH")
   305  		if !ok {
   306  			return "", "", errors.New("environment variable TEST_GERRIT_AUTH is not set")
   307  		}
   308  	}
   309  	f := strings.SplitN(strings.TrimSpace(slurp), ":", 2)
   310  	if len(f) == 1 {
   311  		// assume the whole thing is the token
   312  		return "git-gobot.golang.org", f[0], nil
   313  	}
   314  	if len(f) != 2 || f[0] == "" || f[1] == "" {
   315  		return "", "", fmt.Errorf("Expected Gerrit token %q to be of form <git-email>:<token>", slurp)
   316  	}
   317  	return f[0], f[1], nil
   318  }
   319  
   320  // Hit the Gerrit API and compare its computation of CLs' hashtags against what maintner thinks.
   321  // Off by default unless $TEST_GERRIT_AUTH is defined with "user:token", or we're running in the
   322  // prod project.
   323  func TestGerritHashtags(t *testing.T) {
   324  	if testing.Short() {
   325  		t.Skip("skipping in short mode")
   326  	}
   327  	c := getGoData(t)
   328  	user, pass, err := getGerritAuth()
   329  	if err != nil {
   330  		t.Skipf("no Gerrit auth defined, skipping: %v", err)
   331  	}
   332  	gc := gerrit.NewClient("https://go-review.googlesource.com", gerrit.BasicAuth(user, pass))
   333  	ctx := context.Background()
   334  	more := true
   335  	n := 0
   336  	for more {
   337  		// We search Gerrit for "hashtag", which seems to also
   338  		// search auto-generated gerrit meta (notedb) texts,
   339  		// so this has the effect of searching for all Gerrit
   340  		// changes that have ever had hashtags added or
   341  		// removed:
   342  		cis, err := gc.QueryChanges(ctx, "hashtag", gerrit.QueryChangesOpt{
   343  			Start: n,
   344  		})
   345  		if err != nil {
   346  			t.Fatal(err)
   347  		}
   348  		for _, ci := range cis {
   349  			n++
   350  			cl := c.Gerrit().Project("go.googlesource.com", ci.Project).CL(int32(ci.ChangeNumber))
   351  			if cl == nil {
   352  				t.Logf("Ignoring not-in-maintner %s/%v", ci.Project, ci.ChangeNumber)
   353  				continue
   354  			}
   355  			sort.Strings(ci.Hashtags)
   356  			want := strings.Join(ci.Hashtags, ", ")
   357  			got := canonicalTagList(string(cl.Meta.Hashtags()))
   358  			if got != want {
   359  				t.Errorf("ci: https://golang.org/cl/%d (%s) -- maintner = %q; want gerrit value %q", ci.ChangeNumber, ci.Project, got, want)
   360  			}
   361  			more = ci.MoreChanges
   362  		}
   363  	}
   364  	t.Logf("N = %v", n)
   365  }
   366  
   367  func canonicalTagList(s string) string {
   368  	var sl []string
   369  	for _, v := range strings.Split(s, ",") {
   370  		sl = append(sl, strings.TrimSpace(v))
   371  	}
   372  	sort.Strings(sl)
   373  	return strings.Join(sl, ", ")
   374  }