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 }