golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/maintnerd/maintapi/api_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 maintapi 6 7 import ( 8 "context" 9 "encoding/hex" 10 "flag" 11 "fmt" 12 "strconv" 13 "strings" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/golang/protobuf/proto" 19 "github.com/google/go-cmp/cmp" 20 "golang.org/x/build/gerrit" 21 "golang.org/x/build/maintner" 22 "golang.org/x/build/maintner/godata" 23 "golang.org/x/build/maintner/maintnerd/apipb" 24 "google.golang.org/grpc" 25 "google.golang.org/grpc/codes" 26 "google.golang.org/protobuf/testing/protocmp" 27 ) 28 29 func TestGetRef(t *testing.T) { 30 c := getGoData(t) 31 s := apiService{c: c} 32 req := &apipb.GetRefRequest{ 33 GerritServer: "go.googlesource.com", 34 GerritProject: "go", 35 Ref: "refs/heads/master", 36 } 37 res, err := s.GetRef(context.Background(), req) 38 if err != nil { 39 t.Fatal(err) 40 } 41 if len(res.Value) != 40 { 42 t.Errorf("go master ref = %q; want length 40 string", res.Value) 43 } 44 45 // Bogus ref 46 req.Ref = "NOT EXIST REF" 47 res, err = s.GetRef(context.Background(), req) 48 if err != nil { 49 t.Fatal(err) 50 } 51 if len(res.Value) != 0 { 52 t.Errorf("go bogus ref = %q; want empty string", res.Value) 53 } 54 55 // Bogus project 56 req.GerritProject = "NOT EXIST PROJ" 57 _, err = s.GetRef(context.Background(), req) 58 if got, want := fmt.Sprint(err), "unknown gerrit project"; got != want { 59 t.Errorf("error for bogus project = %q; want %q", got, want) 60 } 61 } 62 63 var hitGerrit = flag.Bool("hit_gerrit", false, "query production Gerrit in TestFindTryWork") 64 65 func TestFindTryWork(t *testing.T) { 66 if !*hitGerrit { 67 t.Skip("skipping without flag -hit_gerrit") 68 } 69 c := getGoData(t) 70 s := apiService{c: c} 71 req := &apipb.GoFindTryWorkRequest{} 72 t0 := time.Now() 73 res, err := s.GoFindTryWork(context.Background(), req) 74 d0 := time.Since(t0) 75 if err != nil { 76 t.Fatal(err) 77 } 78 79 // Just for interactive debugging. This is using live data. 80 // The stable tests are in TestTryWorkItem and TestTryBotStatus. 81 t.Logf("Current:\n%v", proto.MarshalTextString(res)) 82 83 t1 := time.Now() 84 res2, err := s.GoFindTryWork(context.Background(), req) 85 d1 := time.Since(t1) 86 t.Logf("Latency: %v, then %v", d0, d1) 87 t.Logf("Cached: equal=%v, err=%v", proto.Equal(res, res2), err) 88 } 89 90 func TestTryBotStatus(t *testing.T) { 91 c := getGoData(t) 92 tests := []struct { 93 proj string 94 clnum int32 95 msgCutoff int 96 wantTry bool 97 wantDone bool 98 }{ 99 {"go", 51430, 1, true, false}, 100 {"go", 51430, 2, true, false}, 101 {"go", 51430, 3, true, true}, 102 103 {"build", 48968, 5, true, false}, // adding trybot (coordinator ignores for "build" repo) 104 {"build", 48968, 6, false, false}, // removing it 105 } 106 for _, tt := range tests { 107 cl := c.Gerrit().Project("go.googlesource.com", tt.proj).CL(tt.clnum) 108 if cl == nil { 109 t.Errorf("CL %d in %s not found", tt.clnum, tt.proj) 110 continue 111 } 112 old := *cl // save before mutations 113 cl.Version = cl.Messages[tt.msgCutoff-1].Version 114 cl.Messages = cl.Messages[:tt.msgCutoff] 115 gotTry, gotDone := tryBotStatus(cl, false /* not staging */) 116 if gotTry != tt.wantTry || gotDone != tt.wantDone { 117 t.Errorf("tryBotStatus(%q, %d) after %d messages = try/done %v, %v; want %v, %v", 118 tt.proj, tt.clnum, tt.msgCutoff, gotTry, gotDone, tt.wantTry, tt.wantDone) 119 for _, msg := range cl.Messages { 120 t.Logf(" msg ver=%d, text=%q", msg.Version, msg.Message) 121 } 122 } 123 *cl = old // restore 124 } 125 } 126 127 func TestTryWorkItem(t *testing.T) { 128 c := getGoData(t) 129 goProj := gerritProject{ 130 refs: []refHash{ 131 {"refs/heads/master", gitHash("9995c6b50aa55c1cc1236d1d688929df512dad53")}, 132 {"refs/heads/release-branch.go1.16", gitHash("e67a58b7cb2b228e04477dfdb1aacd8348e63534")}, 133 {"refs/heads/release-branch.go1.15", gitHash("72ccabc99449b2cb5bb1438eb90244d55f7b02f5")}, 134 }, 135 } 136 develVersion := apipb.MajorMinor{ 137 Major: 1, Minor: 17, 138 } 139 supportedReleases := []*apipb.GoRelease{ 140 { 141 Major: 1, Minor: 16, Patch: 3, 142 TagName: "go1.16.3", 143 TagCommit: "9baddd3f21230c55f0ad2a10f5f20579dcf0a0bb", 144 BranchName: "release-branch.go1.16", 145 BranchCommit: "e67a58b7cb2b228e04477dfdb1aacd8348e63534", 146 }, 147 { 148 Major: 1, Minor: 15, Patch: 11, 149 TagName: "go1.15.11", 150 TagCommit: "8c163e85267d146274f68854fe02b4a495586584", 151 BranchName: "release-branch.go1.15", 152 BranchCommit: "72ccabc99449b2cb5bb1438eb90244d55f7b02f5", 153 }, 154 } 155 tests := []struct { 156 proj string 157 clnum int32 158 ci *gerrit.ChangeInfo 159 comments map[string][]gerrit.CommentInfo 160 want *apipb.GerritTryWorkItem 161 }{ 162 // Same Change-Id, different branch: 163 {"go", 51430, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 164 Project: "go", 165 Branch: "master", 166 ChangeId: "I0bcae339624e7d61037d9ea0885b7bd07491bbb6", 167 Commit: "45a4609c0ae214e448612e0bc0846e2f2682f1b2", 168 AuthorEmail: "bradfitz@golang.org", 169 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 170 }}, 171 {"go", 51450, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 172 Project: "go", 173 Branch: "release-branch.go1.9", 174 ChangeId: "I0bcae339624e7d61037d9ea0885b7bd07491bbb6", 175 Commit: "7320506bc58d3a55eff2c67b2ec65cfa94f7b0a7", 176 AuthorEmail: "bradfitz@golang.org", 177 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 9}}, 178 }}, 179 // Different project: Tested on tip and two supported releases. 180 {"build", 51432, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 181 Project: "build", 182 Branch: "master", 183 ChangeId: "I1f71836da7008e58d3e76e2cc3170e96cd57ddf6", 184 Commit: "9251bc9950baff61d95da0761e2e4bfab61ed210", 185 AuthorEmail: "bradfitz@golang.org", 186 GoCommit: []string{ 187 "9995c6b50aa55c1cc1236d1d688929df512dad53", 188 "e67a58b7cb2b228e04477dfdb1aacd8348e63534", 189 "72ccabc99449b2cb5bb1438eb90244d55f7b02f5", 190 }, 191 GoBranch: []string{"master", "release-branch.go1.16", "release-branch.go1.15"}, 192 GoVersion: []*apipb.MajorMinor{ 193 {Major: 1, Minor: 17}, 194 {Major: 1, Minor: 16}, 195 {Major: 1, Minor: 15}, 196 }, 197 }}, 198 199 // Test that a golang.org/x repo TryBot on a branch like 200 // "internal-branch.go1.N-suffix" tests with Go 1.N (rather than tip + two supported releases). 201 // See issues 28891, 42127, and 36882. 202 {"net", 314649, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 203 Project: "net", 204 Branch: "internal-branch.go1.16-vendor", 205 ChangeId: "I2c54ce3b2acf1c5efdea66db0595b93a3f5ae5f3", 206 Commit: "3f4a416c7d3b3b41375d159f71ff0a801fc0102b", 207 AuthorEmail: "katie@golang.org", 208 GoCommit: []string{"e67a58b7cb2b228e04477dfdb1aacd8348e63534"}, 209 GoBranch: []string{"release-branch.go1.16"}, 210 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 16}}, 211 }}, 212 213 // Test that TryBots run on branches of the x/ repositories, other than 214 // "master" and "release-branch.go1.N". See issue 37512. 215 {"tools", 238259, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 216 Project: "tools", 217 Branch: "dev.go2go", 218 ChangeId: "I24950593b517af011a636966cb98b9652d2c4134", 219 Commit: "76e917206452e73dc28cbeb58a15ea8f30487263", 220 AuthorEmail: "rstambler@golang.org", 221 GoCommit: []string{"9995c6b50aa55c1cc1236d1d688929df512dad53"}, 222 GoBranch: []string{"master"}, 223 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 224 }}, 225 226 // Test that x/tools TryBots on gopls release branches are 227 // tested on tip and two supported releases. See issue 46156. 228 {"tools", 316773, &gerrit.ChangeInfo{}, nil, &apipb.GerritTryWorkItem{ 229 Project: "tools", 230 Branch: "gopls-release-branch.0.6", 231 ChangeId: "I32fd2c0d30854e61109ebd16a05d5099f9074fe5", 232 Commit: "0bb7e5c47b1a31f85d4f173edc878a8e049764a5", 233 AuthorEmail: "rstambler@golang.org", 234 GoCommit: []string{ 235 "9995c6b50aa55c1cc1236d1d688929df512dad53", 236 "e67a58b7cb2b228e04477dfdb1aacd8348e63534", 237 "72ccabc99449b2cb5bb1438eb90244d55f7b02f5", 238 }, 239 GoBranch: []string{"master", "release-branch.go1.16", "release-branch.go1.15"}, 240 GoVersion: []*apipb.MajorMinor{ 241 {Major: 1, Minor: 17}, 242 {Major: 1, Minor: 16}, 243 {Major: 1, Minor: 15}, 244 }, 245 }}, 246 247 // With comments: 248 { 249 proj: "go", 250 clnum: 201203, 251 ci: &gerrit.ChangeInfo{ 252 CurrentRevision: "f99d33e72efdea68fce39765bc94479b5ebed0a9", 253 Revisions: map[string]gerrit.RevisionInfo{ 254 "f99d33e72efdea68fce39765bc94479b5ebed0a9": {PatchSetNumber: 88}, 255 }, 256 Messages: []gerrit.ChangeMessageInfo{ 257 { 258 Author: &gerrit.AccountInfo{NumericID: 1234}, 259 Message: "Patch Set 1: Run-TryBot+1\n\n(1 comment)", 260 Time: gerrit.TimeStamp(time.Date(2020, 7, 7, 23, 27, 23, 0, time.UTC)), 261 RevisionNumber: 1, 262 }, 263 { 264 Author: &gerrit.AccountInfo{NumericID: 5678}, 265 Message: "Patch Set 2: Foo-2 Run-TryBot+1\n\n(1 comment)", 266 Time: gerrit.TimeStamp(time.Date(2020, 7, 7, 23, 28, 47, 0, time.UTC)), 267 RevisionNumber: 2, 268 }, 269 }, 270 }, 271 comments: map[string][]gerrit.CommentInfo{ 272 "/PATCHSET_LEVEL": { 273 { 274 PatchSet: 1, 275 Message: "TRY=foo", 276 Updated: gerrit.TimeStamp(time.Date(2020, 7, 7, 23, 27, 23, 0, time.UTC)), 277 Author: &gerrit.AccountInfo{NumericID: 1234}, 278 }, 279 { 280 PatchSet: 2, 281 Message: "A preceding sentence.\nTRY=bar, baz\nA following sentence.", 282 Updated: gerrit.TimeStamp(time.Date(2020, 7, 7, 23, 28, 47, 0, time.UTC)), 283 Author: &gerrit.AccountInfo{NumericID: 5678}, 284 }, 285 }, 286 }, 287 want: &apipb.GerritTryWorkItem{ 288 Project: "go", 289 Branch: "master", 290 ChangeId: "I358eb7b11768df8c80fb7e805abd4cd01d52bb9b", 291 Commit: "f99d33e72efdea68fce39765bc94479b5ebed0a9", 292 AuthorEmail: "bradfitz@golang.org", 293 Version: 88, 294 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 295 TryMessage: []*apipb.TryVoteMessage{ 296 {Message: "foo", AuthorId: 1234, Version: 1}, 297 {Message: "bar, baz", AuthorId: 5678, Version: 2}, 298 }, 299 }, 300 }, 301 302 // Test that followup TRY= requests on the same patch set are included. See issue 42084. 303 { 304 proj: "go", 305 clnum: 324763, 306 ci: &gerrit.ChangeInfo{ 307 CurrentRevision: "dd38fd80c3667f891dbe06bd1d8ed153c2e208da", 308 Revisions: map[string]gerrit.RevisionInfo{ 309 "dd38fd80c3667f891dbe06bd1d8ed153c2e208da": {PatchSetNumber: 1}, 310 }, 311 Messages: []gerrit.ChangeMessageInfo{ 312 { 313 Author: &gerrit.AccountInfo{NumericID: 1234}, 314 Message: "Patch Set 1: Run-TryBot+1 Trust+1\n\n(1 comment)", 315 Time: gerrit.TimeStamp(time.Date(2021, 6, 3, 18, 58, 0, 0, time.UTC)), 316 RevisionNumber: 1, 317 }, 318 { 319 Author: &gerrit.AccountInfo{NumericID: 1234}, 320 Message: "Patch Set 1: Run-TryBot+1\n\n(1 comment)", 321 Time: gerrit.TimeStamp(time.Date(2021, 6, 3, 19, 16, 26, 0, time.UTC)), 322 RevisionNumber: 1, 323 }, 324 }, 325 }, 326 comments: map[string][]gerrit.CommentInfo{ 327 "/PATCHSET_LEVEL": { 328 { 329 PatchSet: 1, 330 Message: "TRY=windows-arm64,windows-amd64", 331 Updated: gerrit.TimeStamp(time.Date(2021, 6, 3, 18, 58, 0, 0, time.UTC)), 332 Author: &gerrit.AccountInfo{NumericID: 1234}, 333 }, 334 { 335 PatchSet: 1, 336 Message: "TRY=windows-arm64-10", 337 Updated: gerrit.TimeStamp(time.Date(2021, 6, 3, 19, 16, 26, 0, time.UTC)), 338 Author: &gerrit.AccountInfo{NumericID: 1234}, 339 }, 340 }, 341 }, 342 want: &apipb.GerritTryWorkItem{ 343 Project: "go", 344 Branch: "master", 345 ChangeId: "I023d5208374f867552ba68b45011f7990159868f", 346 Commit: "dd38fd80c3667f891dbe06bd1d8ed153c2e208da", 347 AuthorEmail: "thanm@google.com", 348 Version: 1, 349 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 350 TryMessage: []*apipb.TryVoteMessage{ 351 {Message: "windows-arm64,windows-amd64", AuthorId: 1234, Version: 1}, 352 {Message: "windows-arm64-10", AuthorId: 1234, Version: 1}, 353 }, 354 }, 355 }, 356 357 // Test that TRY= request messages with an older patchset-level comment are included. 358 // See https://go-review.googlesource.com/c/go/+/493535/comments/c72580be_773332cb where 359 // a Run-TryBot+1 request is posted on PS 2 with a patchset-level comment left on PS 1. 360 { 361 proj: "go", 362 clnum: 493535, 363 ci: &gerrit.ChangeInfo{ 364 CurrentRevision: "f8aa751e53d7019eb1114da68754c77cc0830163", 365 Revisions: map[string]gerrit.RevisionInfo{ 366 "a2afb09fc37fcff8ff43d895def78274d6ec4d74": {PatchSetNumber: 1}, 367 "f8aa751e53d7019eb1114da68754c77cc0830163": {PatchSetNumber: 2}, 368 }, 369 Messages: []gerrit.ChangeMessageInfo{ 370 // A message posted a minute after PS 2 was uploaded. 371 { 372 Author: &gerrit.AccountInfo{NumericID: 1234}, 373 Message: "Patch Set 2: Code-Review+2 Run-TryBot+1\n\n(1 comment)", 374 Time: gerrit.TimeStamp(time.Date(2023, 5, 8, 16, 14, 3, 0, time.UTC)), 375 RevisionNumber: 2, 376 }, 377 }, 378 }, 379 comments: map[string][]gerrit.CommentInfo{ 380 "/PATCHSET_LEVEL": { 381 // Its patchset-level comment is associated with PS 1. 382 { 383 PatchSet: 1, 384 Message: "TRY\u003dplan9\n\nThanks!", 385 Updated: gerrit.TimeStamp(time.Date(2023, 5, 8, 16, 14, 3, 0, time.UTC)), 386 Author: &gerrit.AccountInfo{NumericID: 1234}, 387 }, 388 }, 389 }, 390 want: &apipb.GerritTryWorkItem{ 391 Project: "go", 392 Branch: "master", 393 ChangeId: "Ia30f51307cc6d07a7e3ada6bf9d60bf9951982ff", 394 Commit: "f8aa751e53d7019eb1114da68754c77cc0830163", 395 AuthorEmail: "millerresearch@gmail.com", 396 Version: 2, 397 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 398 TryMessage: []*apipb.TryVoteMessage{ 399 {Message: "plan9", AuthorId: 1234, Version: 2}, 400 }, 401 }, 402 }, 403 } 404 for _, tt := range tests { 405 t.Run(strconv.Itoa(int(tt.clnum)), func(t *testing.T) { 406 cl := c.Gerrit().Project("go.googlesource.com", tt.proj).CL(tt.clnum) 407 if cl == nil { 408 t.Fatalf("CL %d in %s not found", tt.clnum, tt.proj) 409 } 410 work, err := tryWorkItem(cl, tt.ci, tt.comments, goProj, develVersion, supportedReleases) 411 if err != nil { 412 t.Fatalf("tryWorkItem(%q, %v, ...): err=%v", tt.proj, tt.clnum, err) 413 } 414 if len(work.GoVersion) == 0 { 415 t.Errorf("tryWorkItem(%q, %v, ...): len(GoVersion) is zero, want at least one", tt.proj, tt.clnum) 416 } 417 if work.Project != "go" && (len(work.GoCommit) == 0 || len(work.GoBranch) == 0) { 418 t.Errorf("tryWorkItem(%q, %v, ...): GoCommit/GoBranch slice is empty for x/ repo, want both non-empty", tt.proj, tt.clnum) 419 } 420 if len(work.GoBranch) != len(work.GoCommit) { 421 t.Errorf("tryWorkItem(%q, %v, ...): bad correlation between GoBranch and GoCommit slices", tt.proj, tt.clnum) 422 } 423 if ok := len(work.GoVersion) == len(work.GoCommit) || (len(work.GoVersion) == 1 && len(work.GoCommit) == 0); !ok { 424 t.Errorf("tryWorkItem(%q, %v, ...): bad correlation between GoVersion and GoCommit slices", tt.proj, tt.clnum) 425 } 426 if diff := cmp.Diff(tt.want, work, protocmp.Transform()); diff != "" { 427 t.Errorf("tryWorkItem(%q, %v, ...) mismatch (-want +got):\n%s", tt.proj, tt.clnum, diff) 428 } 429 }) 430 } 431 } 432 433 func TestParseInternalBranchVersion(t *testing.T) { 434 tests := []struct { 435 name string 436 wantMaj int32 437 wantMin int32 438 wantOK bool 439 }{ 440 {"internal-branch.go1.16-vendor", 1, 16, true}, 441 {"internal-branch.go1.16-", 0, 0, false}, // Empty suffix is rejected. 442 {"internal-branch.go1.16", 0, 0, false}, // No suffix is rejected. 443 {"not-internal-branch", 0, 0, false}, 444 {"internal-branch.go1.16.2", 0, 0, false}, 445 {"internal-branch.go42-suffix", 42, 0, true}, // Be ready in case Go 42 is released after 7.5 million years. 446 } 447 for _, tt := range tests { 448 t.Run(tt.name, func(t *testing.T) { 449 maj, min, ok := parseInternalBranchVersion(tt.name) 450 if ok != tt.wantOK || maj != tt.wantMaj || min != tt.wantMin { 451 t.Errorf("parseInternalBranchVersion(%q) = Go %v.%v ok=%v; want Go %v.%v ok=%v", tt.name, 452 maj, min, ok, tt.wantMaj, tt.wantMin, tt.wantOK) 453 } 454 }) 455 } 456 } 457 458 var ( 459 corpusMu sync.Mutex 460 corpusCache *maintner.Corpus 461 ) 462 463 func getGoData(tb testing.TB) *maintner.Corpus { 464 if testing.Short() { 465 tb.Skip("skipping test requiring large download in short mode") 466 } 467 corpusMu.Lock() 468 defer corpusMu.Unlock() 469 if corpusCache != nil { 470 return corpusCache 471 } 472 var err error 473 corpusCache, err = godata.Get(context.Background()) 474 if err != nil { 475 tb.Fatalf("getting corpus: %v", err) 476 } 477 return corpusCache 478 } 479 480 func TestSupportedGoReleases(t *testing.T) { 481 tests := []struct { 482 goProj nonChangeRefLister 483 want []*apipb.GoRelease 484 }{ 485 // A sample of real data from maintner. 486 { 487 goProj: gerritProject{ 488 refs: []refHash{ 489 {"HEAD", gitHash("5168fcf63f5001b38f9ac64ce5c5e3c2d397363d")}, 490 {"refs/heads/dev.boringcrypto", gitHash("13bf5b80e8d8841a2a3c9b0d5dec65a0c8636253")}, 491 {"refs/heads/dev.boringcrypto.go1.10", gitHash("2e2a04a605b6c3fc6e733810bdcd0200d8ed25a8")}, 492 {"refs/heads/dev.boringcrypto.go1.11", gitHash("685dc1638240af70c86a146b0ddb86d51d64f269")}, 493 {"refs/heads/dev.typealias", gitHash("8a5ef1501dee0715093e87cdc1c9b6becb81c882")}, 494 {"refs/heads/master", gitHash("5168fcf63f5001b38f9ac64ce5c5e3c2d397363d")}, 495 {"refs/heads/release-branch.go1", gitHash("08b97d4061dd75ceec1d44e4335183cd791c9306")}, 496 {"refs/heads/release-branch.go1.1", gitHash("1d6d8fca241bb611af51e265c1b5a2e9ae904702")}, 497 {"refs/heads/release-branch.go1.10", gitHash("e97b7d68f107ff60152f5bd5701e0286f221ee93")}, 498 {"refs/heads/release-branch.go1.11", gitHash("97781d2ed116d2cd9cb870d0b84fc0ec598c9abc")}, 499 {"refs/heads/release-branch.go1.10-security", gitHash("25ca8f49c3fc4a68daff7a23ab613e3453be5cda")}, 500 {"refs/heads/release-branch.go1.11-security", gitHash("90c896448691b5edb0ab11110f37234f63cd28ed")}, 501 {"refs/heads/release-branch.go1.2", gitHash("43d00b0942c1c6f43993ac71e1eea48e62e22b8d")}, 502 {"refs/heads/release-branch.r59", gitHash("5d9765785dff74784bbdad43f7847b6825509032")}, 503 {"refs/heads/release-branch.r60", gitHash("394b383a1ee0ac3fec5e453a7dbe590d3ce6d6b0")}, 504 {"refs/notes/review", gitHash("c46ab9dacb2ac618d86f1c1f719bc2de46010e86")}, 505 {"refs/tags/1.10beta1.mailed", gitHash("2df74db61620771e4f878c9e1db7aeecc00808ba")}, 506 {"refs/tags/andybons/blog.mailed", gitHash("707a89416af909a3af6c26df93995bc17bf9ce81")}, 507 {"refs/tags/go1", gitHash("6174b5e21e73714c63061e66efdbe180e1c5491d")}, 508 {"refs/tags/go1.0.1", gitHash("2fffba7fe19690e038314d17a117d6b87979c89f")}, 509 {"refs/tags/go1.0.2", gitHash("cb6c6570b73a1c4d19cad94570ed277f7dae55ac")}, 510 {"refs/tags/go1.0.3", gitHash("30be9b4313622c2077539e68826194cb1028c691")}, 511 {"refs/tags/go1.1", gitHash("205f850ceacfc39d1e9d76a9569416284594ce8c")}, 512 {"refs/tags/go1.10", gitHash("bf86aec25972f3a100c3aa58a6abcbcc35bdea49")}, 513 {"refs/tags/go1.10.1", gitHash("ac7c0ee26dda18076d5f6c151d8f920b43340ae3")}, 514 {"refs/tags/go1.10.2", gitHash("71bdbf431b79dff61944f22c25c7e085ccfc25d5")}, 515 {"refs/tags/go1.10.3", gitHash("fe8a0d12b14108cbe2408b417afcaab722b0727c")}, 516 {"refs/tags/go1.10.4", gitHash("2191fce26a7fd1cd5b4975e7bd44ab44b1d9dd78")}, 517 {"refs/tags/go1.10beta1", gitHash("9ce6b5c2ed5d3d5251b9a6a0c548d5fb2c8567e8")}, 518 {"refs/tags/go1.10beta2", gitHash("594668a5a96267a46282ce3007a584ec07adf705")}, 519 {"refs/tags/go1.10rc1", gitHash("5348aed83e39bd1d450d92d7f627e994c2db6ebf")}, 520 {"refs/tags/go1.10rc2", gitHash("20e228f2fdb44350c858de941dff4aea9f3127b8")}, 521 {"refs/tags/go1.11", gitHash("41e62b8c49d21659b48a95216e3062032285250f")}, 522 {"refs/tags/go1.11.1", gitHash("26957168c4c0cdcc7ca4f0b19d0eb19474d224ac")}, 523 {"refs/tags/go1.11beta1", gitHash("a12c1f26e4cc602dae62ec065a237172a5b8f926")}, 524 {"refs/tags/go1.11beta2", gitHash("c814ac44c0571f844718f07aa52afa47e37fb1ed")}, 525 {"refs/tags/go1.11beta3", gitHash("1b870077c896379c066b41657d3c9062097a6943")}, 526 {"refs/tags/go1.11rc1", gitHash("807e7f2420c683384dc9c6db498808ba1b7aab17")}, 527 {"refs/tags/go1.11rc2", gitHash("02c0c32960f65d0b9c66ec840c612f5f9623dc51")}, 528 {"refs/tags/go1.9.7", gitHash("7df09b4a03f9e53334672674ba7983d5e7128646")}, 529 {"refs/tags/go1.9beta1", gitHash("952ecbe0a27aadd184ca3e2c342beb464d6b1653")}, 530 {"refs/tags/go1.9beta2", gitHash("eab99a8d548f8ba864647ab171a44f0a5376a6b3")}, 531 {"refs/tags/go1.9rc1", gitHash("65c6c88a9442b91d8b2fd0230337b1fda4bb6cdf")}, 532 {"refs/tags/go1.9rc2", gitHash("048c9cfaacb6fe7ac342b0acd8ca8322b6c49508")}, 533 {"refs/tags/release.r59", gitHash("5d9765785dff74784bbdad43f7847b6825509032")}, 534 {"refs/tags/release.r60", gitHash("5464bfebe723752dfc09a6dd6b361b8e79db5995")}, 535 {"refs/tags/release.r60.1", gitHash("4af7136fcf874e212d66c72178a68db969918b25")}, 536 {"refs/tags/weekly", gitHash("3895b5051df256b442d0b0af50debfffd8d75164")}, 537 {"refs/tags/weekly.2009-11-10", gitHash("78c47c36b2984058c1bec0bd72e0b127b24fcd44")}, 538 {"refs/tags/weekly.2009-11-10.1", gitHash("c57054f7b49539ca4ed6533267c1c20c39aaaaa5")}, 539 }, 540 }, 541 want: []*apipb.GoRelease{ 542 { 543 Major: 1, Minor: 11, Patch: 1, 544 TagName: "go1.11.1", 545 TagCommit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac", 546 BranchName: "release-branch.go1.11", 547 BranchCommit: "97781d2ed116d2cd9cb870d0b84fc0ec598c9abc", 548 }, 549 { 550 Major: 1, Minor: 10, Patch: 4, 551 TagName: "go1.10.4", 552 TagCommit: "2191fce26a7fd1cd5b4975e7bd44ab44b1d9dd78", 553 BranchName: "release-branch.go1.10", 554 BranchCommit: "e97b7d68f107ff60152f5bd5701e0286f221ee93", 555 }, 556 }, 557 }, 558 559 // Detect and handle a new major version. 560 { 561 goProj: gerritProject{ 562 refs: []refHash{ 563 {"refs/tags/go1.5", gitHash("9b82ca331d1fa30e3428e7914ba780ae7f75a702")}, 564 {"refs/tags/go1.42.1", gitHash("23982c09ae5ac811d1dd0099e1626596ade61000")}, 565 {"refs/tags/go1", gitHash("5c503fde0aa534d3259533802052f936c95fa782")}, 566 {"refs/tags/go2", gitHash("43126518de2eb0dadc0917a593f08637318986bf")}, 567 {"refs/tags/go1.11.111", gitHash("c59f000d9bb66592ff84a942014afd1a7be4c953")}, // The onesiest release ever! 568 {"refs/heads/release-branch.go1", gitHash("b0f2d801c19fc8798ecf67e50364a44dba606fcd")}, 569 {"refs/heads/release-branch.go1.5", gitHash("a6ae58c93408bcc17758d397eed0ace973de8481")}, 570 {"refs/heads/release-branch.go1.11", gitHash("f4f148ef7962271ff8ffcebf13400ded535e9957")}, 571 {"refs/heads/release-branch.go1.42", gitHash("362986e7a4b5edc911ed55324c37106c40abe3fb")}, 572 {"refs/heads/release-branch.go2", gitHash("cfbe0f14bcbf1e773f8dd9a968c80cf0b9238c59")}, 573 {"refs/heads/release-branch.go1.2", gitHash("6523e1eb33ef792df04e08462ed332b95311261e")}, 574 575 // It doesn't count as a release if there's no corresponding release-branch.go1.43 release branch. 576 {"refs/tags/go1.43", gitHash("3aa7f7065ecf717b1dd6512bb7a9f40625fc8cb5")}, 577 }, 578 }, 579 want: []*apipb.GoRelease{ 580 { 581 Major: 2, Minor: 0, Patch: 0, 582 TagName: "go2", 583 TagCommit: "43126518de2eb0dadc0917a593f08637318986bf", 584 BranchName: "release-branch.go2", 585 BranchCommit: "cfbe0f14bcbf1e773f8dd9a968c80cf0b9238c59", 586 }, 587 { 588 Major: 1, Minor: 42, Patch: 1, 589 TagName: "go1.42.1", 590 TagCommit: "23982c09ae5ac811d1dd0099e1626596ade61000", 591 BranchName: "release-branch.go1.42", 592 BranchCommit: "362986e7a4b5edc911ed55324c37106c40abe3fb", 593 }, 594 }, 595 }, 596 } 597 for i, tt := range tests { 598 got, err := supportedGoReleases(tt.goProj) 599 if err != nil { 600 t.Fatalf("%d: supportedGoReleases: %v", i, err) 601 } 602 if diff := cmp.Diff(got, tt.want, protocmp.Transform()); diff != "" { 603 t.Errorf("%d: supportedGoReleases: (-got +want)\n%s", i, diff) 604 } 605 } 606 } 607 608 func TestGetDashboard(t *testing.T) { 609 c := getGoData(t) 610 s := apiService{c: c} 611 612 type check func(t *testing.T, res *apipb.DashboardResponse, resErr error) 613 var noError check = func(t *testing.T, res *apipb.DashboardResponse, resErr error) { 614 t.Helper() 615 if resErr != nil { 616 t.Fatalf("GetDashboard: %v", resErr) 617 } 618 } 619 var commitsTruncated check = func(t *testing.T, res *apipb.DashboardResponse, _ error) { 620 t.Helper() 621 if !res.CommitsTruncated { 622 t.Errorf("CommitsTruncated = false; want true") 623 } 624 if len(res.Commits) == 0 { 625 t.Errorf("no commits; expected some commits when expecting CommitsTruncated") 626 } 627 628 } 629 hasBranch := func(branch string) check { 630 return func(t *testing.T, res *apipb.DashboardResponse, _ error) { 631 ok := false 632 for _, b := range res.Branches { 633 if b == branch { 634 ok = true 635 break 636 } 637 } 638 if !ok { 639 t.Errorf("didn't find expected branch %q; got branches: %q", branch, res.Branches) 640 } 641 } 642 } 643 hasRepoHead := func(proj string) check { 644 return func(t *testing.T, res *apipb.DashboardResponse, _ error) { 645 ok := false 646 var got []string 647 for _, rh := range res.RepoHeads { 648 if rh.GerritProject == proj { 649 ok = true 650 } 651 got = append(got, rh.GerritProject) 652 } 653 if !ok { 654 t.Errorf("didn't find expected repo head %q; got: %q", proj, got) 655 } 656 } 657 } 658 var hasThreeReleases check = func(t *testing.T, res *apipb.DashboardResponse, _ error) { 659 t.Helper() 660 var got []string 661 var gotMaster int 662 var gotReleaseBranch int 663 var uniq = map[string]bool{} 664 for _, r := range res.Releases { 665 got = append(got, r.BranchName) 666 uniq[r.BranchName] = true 667 if r.BranchName == "master" { 668 gotMaster++ 669 } 670 if strings.HasPrefix(r.BranchName, "release-branch.go") { 671 gotReleaseBranch++ 672 } 673 } 674 if len(uniq) != 3 { 675 t.Errorf("expected 3 Go releases, got: %q", got) 676 } 677 if gotMaster != 1 { 678 t.Errorf("expected 1 Go release to be master, got: %q", got) 679 } 680 if gotReleaseBranch != 2 { 681 t.Errorf("expected 2 Go releases to be release branches, got: %q", got) 682 } 683 } 684 wantRPCError := func(code codes.Code) check { 685 return func(t *testing.T, _ *apipb.DashboardResponse, err error) { 686 if grpc.Code(err) != code { 687 t.Errorf("expected RPC code %v; got %v (err %v)", code, grpc.Code(err), err) 688 } 689 } 690 } 691 basicChecks := []check{ 692 noError, 693 commitsTruncated, 694 hasBranch("master"), 695 hasBranch("release-branch.go1.4"), 696 hasBranch("release-branch.go1.13"), 697 hasRepoHead("net"), 698 hasRepoHead("sys"), 699 hasThreeReleases, 700 } 701 702 tests := []struct { 703 name string 704 req *apipb.DashboardRequest 705 checks []check 706 }{ 707 // Verify that the default view (with no options) works. 708 { 709 name: "zero_value", 710 req: &apipb.DashboardRequest{}, 711 checks: basicChecks, 712 }, 713 // Or with explicit values: 714 { 715 name: "zero_value_effectively", 716 req: &apipb.DashboardRequest{ 717 Repo: "go", 718 Branch: "master", 719 }, 720 checks: basicChecks, 721 }, 722 // Max commits: 723 { 724 name: "max_commits", 725 req: &apipb.DashboardRequest{MaxCommits: 1}, 726 checks: []check{ 727 noError, 728 commitsTruncated, 729 func(t *testing.T, res *apipb.DashboardResponse, _ error) { 730 if got, want := len(res.Commits), 1; got != want { 731 t.Errorf("got %v commits; want %v", got, want) 732 } 733 }, 734 }, 735 }, 736 // Verify that branch=mixed doesn't return an error at least. 737 { 738 name: "mixed", 739 req: &apipb.DashboardRequest{Branch: "mixed"}, 740 checks: []check{ 741 noError, 742 commitsTruncated, 743 hasRepoHead("sys"), 744 hasThreeReleases, 745 }, 746 }, 747 // Verify non-Go repos: 748 { 749 name: "non_go_repo", 750 req: &apipb.DashboardRequest{Repo: "golang.org/x/net"}, 751 checks: []check{ 752 noError, 753 commitsTruncated, 754 func(t *testing.T, res *apipb.DashboardResponse, _ error) { 755 for _, c := range res.Commits { 756 if c.GoCommitAtTime == "" { 757 t.Errorf("response contains commit without GoCommitAtTime") 758 } 759 if c.GoCommitLatest == "" { 760 t.Errorf("response contains commit without GoCommitLatest") 761 } 762 if t.Failed() { 763 return 764 } 765 } 766 }, 767 }, 768 }, 769 770 // Validate rejection of bad requests: 771 { 772 name: "bad-repo", 773 req: &apipb.DashboardRequest{Repo: "NOT_EXIST"}, 774 checks: []check{wantRPCError(codes.NotFound)}, 775 }, 776 { 777 name: "bad-branch", 778 req: &apipb.DashboardRequest{Branch: "NOT_EXIST"}, 779 checks: []check{wantRPCError(codes.NotFound)}, 780 }, 781 { 782 name: "mixed-with-pagination", 783 req: &apipb.DashboardRequest{Branch: "mixed", Page: 5}, 784 checks: []check{wantRPCError(codes.InvalidArgument)}, 785 }, 786 { 787 name: "negative-page", 788 req: &apipb.DashboardRequest{Page: -1}, 789 checks: []check{wantRPCError(codes.InvalidArgument)}, 790 }, 791 { 792 name: "too-big-page", 793 req: &apipb.DashboardRequest{Page: 1e6}, 794 checks: []check{wantRPCError(codes.InvalidArgument)}, 795 }, 796 } 797 798 for _, tt := range tests { 799 t.Run(tt.name, func(t *testing.T) { 800 res, err := s.GetDashboard(context.Background(), tt.req) 801 for _, c := range tt.checks { 802 c(t, res, err) 803 } 804 }) 805 } 806 } 807 808 type gerritProject struct { 809 refs []refHash 810 } 811 812 func (gp gerritProject) Ref(ref string) maintner.GitHash { 813 for _, r := range gp.refs { 814 if r.Ref == ref { 815 return r.Hash 816 } 817 } 818 return "" 819 } 820 821 func (gp gerritProject) ForeachNonChangeRef(fn func(ref string, hash maintner.GitHash) error) error { 822 for _, r := range gp.refs { 823 err := fn(r.Ref, r.Hash) 824 if err != nil { 825 return err 826 } 827 } 828 return nil 829 } 830 831 type refHash struct { 832 Ref string 833 Hash maintner.GitHash 834 } 835 836 func gitHash(hexa string) maintner.GitHash { 837 if len(hexa) != 40 { 838 panic(fmt.Errorf("bogus git hash %q", hexa)) 839 } 840 binary, err := hex.DecodeString(hexa) 841 if err != nil { 842 panic(fmt.Errorf("bogus git hash %q: %v", hexa, err)) 843 } 844 return maintner.GitHash(binary) 845 }