golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/coordinator/coordinator_test.go (about) 1 // Copyright 2015 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 //go:build linux || darwin 6 7 package main 8 9 import ( 10 "bytes" 11 "io" 12 "log" 13 "net/http" 14 "net/http/httptest" 15 "reflect" 16 "strings" 17 "testing" 18 "time" 19 20 "golang.org/x/build/buildenv" 21 "golang.org/x/build/gerrit" 22 "golang.org/x/build/internal/buildgo" 23 "golang.org/x/build/internal/coordinator/pool" 24 "golang.org/x/build/maintner/maintnerd/apipb" 25 ) 26 27 type Seconds float64 28 29 func (s Seconds) Duration() time.Duration { 30 return time.Duration(float64(s) * float64(time.Second)) 31 } 32 33 var fixedTestDuration = map[string]Seconds{ 34 "go_test:a": 1, 35 "go_test:b": 1.5, 36 "go_test:c": 2, 37 "go_test:d": 2.50, 38 "go_test:e": 3, 39 "go_test:f": 3.5, 40 "go_test:g": 4, 41 "go_test:h": 4.5, 42 "go_test:i": 5, 43 "go_test:j": 5.5, 44 "go_test:k": 6.5, 45 } 46 47 func TestPartitionGoTests(t *testing.T) { 48 var in []string 49 for name := range fixedTestDuration { 50 in = append(in, name) 51 } 52 testDuration := func(builder, testName string) time.Duration { 53 if s, ok := fixedTestDuration[testName]; ok { 54 return s.Duration() 55 } 56 return 3 * time.Second 57 } 58 sets := partitionGoTests(testDuration, "", in) 59 want := [][]string{ 60 {"go_test:a", "go_test:b", "go_test:c", "go_test:d", "go_test:e"}, 61 {"go_test:f", "go_test:g"}, 62 {"go_test:h", "go_test:i"}, 63 {"go_test:j"}, 64 {"go_test:k"}, 65 } 66 if !reflect.DeepEqual(sets, want) { 67 t.Errorf(" got: %v\nwant: %v", sets, want) 68 } 69 } 70 71 func TestTryStatusJSON(t *testing.T) { 72 testCases := []struct { 73 desc string 74 method string 75 ts *trySet 76 tss trySetState 77 status int 78 body string 79 }{ 80 { 81 "pre-flight CORS header", 82 "OPTIONS", 83 nil, 84 trySetState{}, 85 http.StatusOK, 86 ``, 87 }, 88 { 89 "nil trySet", 90 "GET", 91 nil, 92 trySetState{}, 93 http.StatusNotFound, 94 `{"success":false,"error":"TryBot result not found (already done, invalid, or not yet discovered from Gerrit). Check Gerrit for results."}` + "\n", 95 }, 96 {"non-nil trySet", 97 "GET", 98 &trySet{ 99 tryKey: tryKey{ 100 Commit: "deadbeef", 101 ChangeID: "Ifoo", 102 }, 103 }, 104 trySetState{ 105 builds: []*buildStatus{ 106 { 107 BuilderRev: buildgo.BuilderRev{Name: "linux"}, 108 startTime: time.Time{}.Add(24 * time.Hour), 109 done: time.Time{}.Add(48 * time.Hour), 110 succeeded: true, 111 }, 112 { 113 BuilderRev: buildgo.BuilderRev{Name: "macOS"}, 114 startTime: time.Time{}.Add(24 * time.Hour), 115 }, 116 }, 117 }, 118 http.StatusOK, 119 `{"success":true,"payload":{"changeId":"Ifoo","commit":"deadbeef","builds":[{"name":"linux","startTime":"0001-01-02T00:00:00Z","done":true,"succeeded":true},{"name":"macOS","startTime":"0001-01-02T00:00:00Z","done":false,"succeeded":false}]}}` + "\n"}, 120 } 121 122 for _, tc := range testCases { 123 t.Run(tc.desc, func(t *testing.T) { 124 w := httptest.NewRecorder() 125 r, err := http.NewRequest(tc.method, "", nil) 126 if err != nil { 127 t.Fatalf("could not create http.Request: %v", err) 128 } 129 serveTryStatusJSON(w, r, tc.ts, tc.tss) 130 resp := w.Result() 131 hdr := "Access-Control-Allow-Origin" 132 if got, want := resp.Header.Get(hdr), "*"; got != want { 133 t.Errorf("unexpected %q header: got %q; want %q", hdr, got, want) 134 } 135 if got, want := resp.StatusCode, tc.status; got != want { 136 t.Errorf("response status code: got %d; want %d", got, want) 137 } 138 defer resp.Body.Close() 139 b, err := io.ReadAll(resp.Body) 140 if err != nil { 141 t.Fatalf("io.ReadAll: %v", err) 142 } 143 if got, want := string(b), tc.body; got != want { 144 t.Errorf("body: got\n%v\nwant\n%v", got, want) 145 } 146 }) 147 } 148 } 149 150 func TestStagingClusterBuilders(t *testing.T) { 151 // Just test that it doesn't panic: 152 stagingClusterBuilders() 153 } 154 155 // Test that trybot on release-branch.go1.N branch of a golang.org/x repo 156 // uses the Go revision from Go repository's release-branch.go1.N branch. 157 // See golang.org/issue/28891. 158 func TestIssue28891(t *testing.T) { 159 testingKnobSkipBuilds = true 160 161 work := &apipb.GerritTryWorkItem{ // Based on what maintapi's GoFindTryWork does for x/net CL 258478. 162 Project: "net", 163 Branch: "release-branch.go1.15", 164 ChangeId: "I546597cedf3715e6617babcb3b62140bf1857a27", 165 Commit: "a5fa9d4b7c91aa1c3fecbeb6358ec1127b910dd6", 166 GoCommit: []string{"72ccabc99449b2cb5bb1438eb90244d55f7b02f5"}, 167 GoBranch: []string{"release-branch.go1.15"}, 168 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 15}}, 169 } 170 ts := newTrySet(work) 171 if len(ts.builds) == 0 { 172 t.Fatal("no builders in try set, want at least 1") 173 } 174 for i, bs := range ts.builds { 175 const go115Revision = "72ccabc99449b2cb5bb1438eb90244d55f7b02f5" 176 if bs.BuilderRev.Rev != go115Revision { 177 t.Errorf("build[%d]: %s: x/net on release-branch.go1.15 branch should be tested with Go 1.15, but isn't", i, bs.NameAndBranch()) 178 } 179 } 180 } 181 182 // Test that trybot on release-branch.go1.N-{suffix} branch of a golang.org/x repo 183 // uses the Go revision from Go repository's release-branch.go1.N branch. 184 // See golang.org/issue/42127. 185 func TestIssue42127(t *testing.T) { 186 testingKnobSkipBuilds = true 187 188 work := &apipb.GerritTryWorkItem{ // Based on what maintapi's GoFindTryWork does for x/net CL 264058. 189 Project: "net", 190 Branch: "release-branch.go1.15-bundle", 191 ChangeId: "I546597cedf3715e6617babcb3b62140bf1857a27", 192 Commit: "abf26a14a65b111d492067f407f32455c5b1048c", 193 GoCommit: []string{"72ccabc99449b2cb5bb1438eb90244d55f7b02f5"}, 194 GoBranch: []string{"release-branch.go1.15"}, 195 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 15}}, 196 } 197 ts := newTrySet(work) 198 if len(ts.builds) == 0 { 199 t.Fatal("no builders in try set, want at least 1") 200 } 201 for i, bs := range ts.builds { 202 const go115Revision = "72ccabc99449b2cb5bb1438eb90244d55f7b02f5" 203 if bs.BuilderRev.Rev != go115Revision { 204 t.Errorf("build[%d]: %s: x/net on release-branch.go1.15-bundle branch should be tested with Go 1.15, but isn't", i, bs.NameAndBranch()) 205 } 206 } 207 } 208 209 // Tests that TryBots run on branches of the x/ repositories, other than 210 // "master" and "release-branch.go1.N". See golang.org/issue/37512. 211 func TestXRepoBranches(t *testing.T) { 212 testingKnobSkipBuilds = true 213 214 work := &apipb.GerritTryWorkItem{ // Based on what maintapi's GoFindTryWork does for x/tools CL 227356. 215 Project: "tools", 216 Branch: "gopls-release-branch.0.4", 217 ChangeId: "Ica799fcf117bf607c0c59f41b08a78552339dc53", 218 Commit: "13af72af5ccdfe6f1e75b57b02cfde3bb0a77a76", 219 GoCommit: []string{"9995c6b50aa55c1cc1236d1d688929df512dad53"}, 220 GoBranch: []string{"master"}, 221 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 222 } 223 ts := newTrySet(work) 224 for i, bs := range ts.builds { 225 v := bs.NameAndBranch() 226 t.Logf("build[%d]: %s", i, v) 227 } 228 if len(ts.builds) < 3 { 229 t.Fatalf("expected at least 3 builders, got %v", len(ts.builds)) 230 } 231 } 232 233 // Test that when there are multiple SlowBot requests on the same patch set, 234 // the latest request is used. See golang.org/issue/42084. 235 func TestIssue42084(t *testing.T) { 236 testingKnobSkipBuilds = true 237 238 work := &apipb.GerritTryWorkItem{ // Based on what maintapi's GoFindTryWork does for CL 324763. TryMessage is set later. 239 Project: "go", 240 Branch: "master", 241 ChangeId: "I023d5208374f867552ba68b45011f7990159868f", 242 Commit: "dd38fd80c3667f891dbe06bd1d8ed153c2e208da", 243 Version: 1, 244 GoCommit: []string{"9995c6b50aa55c1cc1236d1d688929df512dad53"}, 245 GoBranch: []string{"master"}, 246 GoVersion: []*apipb.MajorMinor{{Major: 1, Minor: 17}}, 247 } 248 249 // First, determine builds without try messages. Our target SlowBot shouldn't be included. 250 ts := newTrySet(work) 251 hasLinuxArmBuilder := false 252 for _, bs := range ts.builds { 253 v := bs.NameAndBranch() 254 if v == "linux-arm" { 255 hasLinuxArmBuilder = true 256 } 257 } 258 if hasLinuxArmBuilder { 259 // This test relies on linux-arm builder not being a default 260 // TryBot to provide coverage for issue 42084. If the build policy 261 // changes, need to pick another builder to use in this test. 262 t.Fatal("linux-arm builder was included even without TRY= message") 263 } 264 265 // Next, add try messages, and check that the SlowBot is now included. 266 work.TryMessage = []*apipb.TryVoteMessage{ 267 {Message: "linux", AuthorId: 1234, Version: 1}, 268 {Message: "linux-arm", AuthorId: 1234, Version: 1}, 269 } 270 ts = newTrySet(work) 271 hasLinuxArmBuilder = false 272 for i, bs := range ts.builds { 273 v := bs.NameAndBranch() 274 t.Logf("build[%d]: %s", i, v) 275 if v == "linux-arm-aws" { 276 hasLinuxArmBuilder = true 277 } 278 } 279 if !hasLinuxArmBuilder { 280 t.Error("linux-arm SlowBot was not included") 281 } 282 } 283 284 func TestFindWork(t *testing.T) { 285 if testing.Short() { 286 t.Skip("skipping in short mode") 287 } 288 gce := pool.NewGCEConfiguration() 289 buildEnv := gce.BuildEnv() 290 defer func(old *buildenv.Environment) { gce.SetBuildEnv(old) }(buildEnv) 291 gce.SetBuildEnv(buildenv.Production) 292 defer func() { buildgo.TestHookSnapshotExists = nil }() 293 buildgo.TestHookSnapshotExists = func(br *buildgo.BuilderRev) bool { 294 if strings.Contains(br.Name, "android") { 295 log.Printf("snapshot check for %+v", br) 296 } 297 return false 298 } 299 300 addWorkTestHook = func(work buildgo.BuilderRev, d commitDetail) { 301 t.Logf("Got: %v, %+v", work, d) 302 } 303 defer func() { addWorkTestHook = nil }() 304 305 err := findWork() 306 if err != nil { 307 t.Error(err) 308 } 309 } 310 311 func TestBuildersJSON(t *testing.T) { 312 rec := httptest.NewRecorder() 313 handleBuilders(rec, httptest.NewRequest("GET", "https://farmer.tld/builders?mode=json", nil)) 314 res := rec.Result() 315 if res.Header.Get("Content-Type") != "application/json" || res.StatusCode != 200 { 316 var buf bytes.Buffer 317 res.Write(&buf) 318 t.Error(buf.String()) 319 } 320 } 321 322 func TestSlowBotsFromComments(t *testing.T) { 323 work := &apipb.GerritTryWorkItem{ 324 Version: 2, 325 TryMessage: []*apipb.TryVoteMessage{ 326 { 327 Version: 1, 328 Message: "ios", 329 }, 330 { 331 Version: 2, 332 Message: "arm64, darwin aix ", 333 }, 334 { 335 Version: 1, 336 Message: "aix", 337 }, 338 }, 339 } 340 slowBots, invalidSlowBots := slowBotsFromComments(work) 341 var got []string 342 for _, bc := range slowBots { 343 got = append(got, bc.Name) 344 } 345 want := []string{"aix-ppc64", "darwin-amd64-13", "linux-arm64"} 346 if !reflect.DeepEqual(got, want) { 347 t.Errorf("mismatch:\n got: %q\nwant: %q\n", got, want) 348 } 349 350 if len(invalidSlowBots) > 0 { 351 t.Errorf("mismatch invalidSlowBots:\n got: %d\nwant: 0", len(invalidSlowBots)) 352 } 353 } 354 355 func TestSubreposFromComments(t *testing.T) { 356 work := &apipb.GerritTryWorkItem{ 357 Version: 2, 358 TryMessage: []*apipb.TryVoteMessage{ 359 { 360 Version: 2, 361 Message: "x/build, x/sync x/tools, x/sync, x/tools@freebsd-amd64-race", 362 }, 363 }, 364 } 365 got := xReposFromComments(work) 366 want := map[xRepoAndBuilder]bool{ 367 {"build", ""}: true, 368 {"sync", ""}: true, 369 {"tools", ""}: true, 370 {"tools", "freebsd-amd64-race"}: true, 371 } 372 if !reflect.DeepEqual(got, want) { 373 t.Errorf("mismatch:\n got: %v\nwant: %v\n", got, want) 374 } 375 } 376 377 func TestBuildStatusFormat(t *testing.T) { 378 for i, tt := range []struct { 379 st *buildStatus 380 want string 381 }{ 382 { 383 st: &buildStatus{ 384 trySet: &trySet{ 385 tryKey: tryKey{ 386 Project: "go", 387 }, 388 }, 389 BuilderRev: buildgo.BuilderRev{ 390 Name: "linux-amd64", 391 SubName: "tools", 392 }, 393 commitDetail: commitDetail{ 394 RevBranch: "master", 395 }, 396 }, 397 want: "(x/tools) linux-amd64", 398 }, 399 { 400 st: &buildStatus{ 401 trySet: &trySet{ 402 tryKey: tryKey{ 403 Project: "tools", 404 }, 405 }, 406 BuilderRev: buildgo.BuilderRev{ 407 Name: "linux-amd64", 408 SubName: "tools", 409 }, 410 commitDetail: commitDetail{ 411 RevBranch: "release-branch.go1.15", 412 }, 413 }, 414 want: "linux-amd64 (Go 1.15.x)", 415 }, 416 { 417 st: &buildStatus{ 418 trySet: &trySet{ 419 tryKey: tryKey{ 420 Project: "go", 421 }, 422 }, 423 BuilderRev: buildgo.BuilderRev{ 424 Name: "linux-amd64", 425 SubName: "tools", 426 }, 427 commitDetail: commitDetail{ 428 RevBranch: "master", 429 }, 430 }, 431 want: "(x/tools) linux-amd64", 432 }, 433 { 434 st: &buildStatus{ 435 BuilderRev: buildgo.BuilderRev{ 436 Name: "darwin-amd64-13", 437 }, 438 commitDetail: commitDetail{ 439 RevBranch: "master", 440 }, 441 }, 442 want: "darwin-amd64-13", 443 }, 444 { 445 st: &buildStatus{ 446 BuilderRev: buildgo.BuilderRev{ 447 Name: "darwin-amd64-13", 448 }, 449 commitDetail: commitDetail{ 450 RevBranch: "release-branch.go1.15", 451 }, 452 }, 453 want: "darwin-amd64-13 (Go 1.15.x)", 454 }, 455 } { 456 if got := tt.st.NameAndBranch(); got != tt.want { 457 t.Errorf("%d: NameAndBranch = %q; want %q", i, got, tt.want) 458 } 459 } 460 } 461 462 // listPatchSetThreadsResponse is the response to 463 // https://go-review.googlesource.com/changes/go~master~I92400996cb051ab30e99bfffafd91ff32a1e7087/comments 464 var listPatchSetThreadsResponse = []byte(`)]}' 465 {"/PATCHSET_LEVEL":[{"author":{"_account_id":5976,"name":"Go Bot","email":"gobot@golang.org","tags":["SERVICE_USER"]},"tag":"autogenerated:trybots~beginning","change_message_id":"af128c803aa192eb5c191c1567713f5b123d3adb","unresolved":true,"patch_set":1,"id":"aaf7aa39_658707c2","updated":"2021-04-27 18:20:09.000000000","message":"SlowBots beginning. Status page: https://farmer.golang.org/try?commit\u003d39ad506d","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":5976,"name":"Go Bot","email":"gobot@golang.org","tags":["SERVICE_USER"]},"tag":"autogenerated:trybots~beginning","change_message_id":"a00ee30c652a61afeb5ba7657e5823b2d56159ac","unresolved":true,"patch_set":1,"id":"cb0c9011_d26d6550","updated":"2021-04-27 07:10:41.000000000","message":"SlowBots beginning. Status page: https://farmer.golang.org/try?commit\u003d39ad506d","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":6365,"name":"Bryan C. Mills","email":"bcmills@google.com"},"change_message_id":"c3dc9da7c814efe691876649d8b1acd086d34921","unresolved":false,"patch_set":1,"id":"da1249e7_bc007148","updated":"2021-04-27 07:10:22.000000000","message":"TRY\u003dlongtest","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":6365,"name":"Bryan C. Mills","email":"bcmills@google.com"},"change_message_id":"1908c5ae7cd3fa1adc7579bfb4fa0798a33dafd2","unresolved":false,"patch_set":1,"id":"043375d0_558208b0","in_reply_to":"50a54b3c_f95f3567","updated":"2021-04-27 18:32:56.000000000","message":"#41863","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":6365,"name":"Bryan C. Mills","email":"bcmills@google.com"},"change_message_id":"061f7a6231e09027e96b7c81093b32db9cd6a6f3","unresolved":false,"patch_set":1,"id":"49f73b27_b0261bc8","in_reply_to":"aaf7aa39_658707c2","updated":"2021-04-27 19:17:53.000000000","message":"Ack","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":5976,"name":"Go Bot","email":"gobot@golang.org","tags":["SERVICE_USER"]},"tag":"autogenerated:trybots~failed","change_message_id":"d7e2ff4f58b281bc3120ad79a9c5bf7c87c0ec1b","unresolved":true,"patch_set":1,"id":"50a54b3c_f95f3567","in_reply_to":"c3e462db_b5e1efca","updated":"2021-04-27 07:22:38.000000000","message":"1 of 26 SlowBots failed.\nFailed on linux-arm64-aws: https://storage.googleapis.com/go-build-log/39ad506d/linux-arm64-aws_5dc1efb9.log\n\nConsult https://build.golang.org/ to see whether they are new failures. Keep in mind that TryBots currently test *exactly* your git commit, without rebasing. If your commit\u0027s git parent is old, the failure might\u0027ve already been fixed.\n\nSlowBot builds that ran:\n* linux-amd64-longtest\n","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":5976,"name":"Go Bot","email":"gobot@golang.org","tags":["SERVICE_USER"]},"tag":"autogenerated:trybots~happy","change_message_id":"9ed90c9e9b43e3c2c4d2b6ab8e8a121686d5da88","unresolved":false,"patch_set":1,"id":"13677404_911c1149","in_reply_to":"c3e462db_b5e1efca","updated":"2021-04-27 18:46:54.000000000","message":"SlowBots are happy.\n\nSlowBot builds that ran:\n* linux-amd64-longtest\n","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"},{"author":{"_account_id":5976,"name":"Go Bot","email":"gobot@golang.org","tags":["SERVICE_USER"]},"tag":"autogenerated:trybots~progress","change_message_id":"ab963b29aa95907e30e9f6a51ef1f080807bc8ab","unresolved":true,"patch_set":1,"id":"c3e462db_b5e1efca","in_reply_to":"cb0c9011_d26d6550","updated":"2021-04-27 07:18:11.000000000","message":"Build is still in progress... Status page: https://farmer.golang.org/try?commit\u003d39ad506d\nFailed on linux-arm64-aws: https://storage.googleapis.com/go-build-log/39ad506d/linux-arm64-aws_5dc1efb9.log\nOther builds still in progress; subsequent failure notices suppressed until final report.\n\nConsult https://build.golang.org/ to see whether they are new failures. Keep in mind that TryBots currently test *exactly* your git commit, without rebasing. If your commit\u0027s git parent is old, the failure might\u0027ve already been fixed.\n","commit_id":"39ad506d874d4711015184f52585b4215c9b84cc"}]} 466 `) 467 468 func TestListPatchSetThreads(t *testing.T) { 469 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 470 w.Header().Set("Content-Type", "application/json; charset=UTF-8") 471 w.WriteHeader(200) 472 w.Write(listPatchSetThreadsResponse) 473 })) 474 defer s.Close() 475 gerritClient := gerrit.NewClient(s.URL, gerrit.NoAuth) 476 threads, err := listPatchSetThreads(gerritClient, "go~master~I92400996cb051ab30e99bfffafd91ff32a1e7087") 477 if err != nil { 478 t.Fatal(err) 479 } 480 var mostRecentTryBotThread string 481 for _, tr := range threads { 482 if tr.unresolved { 483 t.Errorf("thread %s is unresolved", tr.root.ID) 484 } 485 if tr.root.Tag == tryBotsTag("beginning") { 486 mostRecentTryBotThread = tr.root.ID 487 } 488 if tr.root != tr.thread[0] { 489 t.Errorf("the root is not the first comment in thread") 490 } 491 } 492 if mostRecentTryBotThread != "aaf7aa39_658707c2" { 493 t.Errorf("wrong most recent TryBot thread: got %s, want %s", mostRecentTryBotThread, "aaf7aa39_658707c2") 494 } 495 } 496 497 func TestInvalidSlowBots(t *testing.T) { 498 work := &apipb.GerritTryWorkItem{ 499 Version: 2, 500 TryMessage: []*apipb.TryVoteMessage{ 501 { 502 Version: 1, 503 Message: "aix, linux-mipps, amd64, freeebsd", 504 }, 505 }, 506 } 507 slowBots, invalidSlowBots := slowBotsFromComments(work) 508 var got []string 509 for _, bc := range slowBots { 510 got = append(got, bc.Name) 511 } 512 want := []string{"aix-ppc64", "linux-amd64"} 513 if !reflect.DeepEqual(got, want) { 514 t.Errorf("mismatch:\n got: %q\nwant: %q\n", got, want) 515 } 516 517 wantInvalid := []string{"linux-mipps", "freeebsd"} 518 if !reflect.DeepEqual(invalidSlowBots, wantInvalid) { 519 t.Errorf("mismatch:\n got: %q\nwant: %q\n", invalidSlowBots, wantInvalid) 520 } 521 }