go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/search/query_test.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package search 16 17 import ( 18 "container/heap" 19 "context" 20 "sort" 21 "testing" 22 "time" 23 24 "google.golang.org/grpc/codes" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 "go.chromium.org/luci/auth/identity" 28 "go.chromium.org/luci/common/clock/testclock" 29 "go.chromium.org/luci/common/data/stringset" 30 "go.chromium.org/luci/common/data/strpair" 31 "go.chromium.org/luci/common/logging/memlogger" 32 "go.chromium.org/luci/gae/impl/memory" 33 "go.chromium.org/luci/gae/service/datastore" 34 "go.chromium.org/luci/server/auth" 35 "go.chromium.org/luci/server/auth/authtest" 36 37 bb "go.chromium.org/luci/buildbucket" 38 "go.chromium.org/luci/buildbucket/appengine/model" 39 "go.chromium.org/luci/buildbucket/bbperms" 40 pb "go.chromium.org/luci/buildbucket/proto" 41 42 . "github.com/smartystreets/goconvey/convey" 43 44 . "go.chromium.org/luci/common/testing/assertions" 45 ) 46 47 const userID = identity.Identity("user:user@example.com") 48 49 func TestNewSearchQuery(t *testing.T) { 50 t.Parallel() 51 52 Convey("NewQuery", t, func() { 53 Convey("valid input", func() { 54 gerritChanges := make([]*pb.GerritChange, 2) 55 gerritChanges[0] = &pb.GerritChange{ 56 Host: "a", 57 Project: "b", 58 Change: 1, 59 Patchset: 1, 60 } 61 gerritChanges[1] = &pb.GerritChange{ 62 Host: "a", 63 Project: "b", 64 Change: 2, 65 Patchset: 1, 66 } 67 tags := []*pb.StringPair{ 68 {Key: "k1", Value: "v1"}, 69 } 70 req := &pb.SearchBuildsRequest{ 71 Predicate: &pb.BuildPredicate{ 72 Builder: &pb.BuilderID{ 73 Project: "infra", 74 Bucket: "ci", 75 Builder: "test", 76 }, 77 Status: pb.Status_ENDED_MASK, 78 GerritChanges: gerritChanges, 79 CreatedBy: "abc@test.com", 80 Tags: tags, 81 CreateTime: &pb.TimeRange{ 82 StartTime: ×tamppb.Timestamp{Seconds: 1592701200}, 83 EndTime: ×tamppb.Timestamp{Seconds: 1592704800}, 84 }, 85 Build: &pb.BuildRange{ 86 StartBuildId: 200, 87 EndBuildId: 100, 88 }, 89 Canary: pb.Trinary_YES, 90 DescendantOf: 2, 91 }, 92 } 93 query := NewQuery(req) 94 95 expectedStartTime := time.Unix(1592701200, 0).UTC() 96 expectedEndTime := time.Unix(1592704800, 0).UTC() 97 expectedTags := strpair.Map{ 98 "k1": []string{"v1"}, 99 "buildset": []string{"patch/gerrit/a/1/1", "patch/gerrit/a/2/1"}, 100 } 101 expectedBuilder := &pb.BuilderID{ 102 Project: "infra", 103 Bucket: "ci", 104 Builder: "test", 105 } 106 107 So(query, ShouldResemble, &Query{ 108 Builder: expectedBuilder, 109 Tags: expectedTags, 110 Status: pb.Status_ENDED_MASK, 111 CreatedBy: identity.Identity("user:abc@test.com"), 112 StartTime: expectedStartTime, 113 EndTime: expectedEndTime, 114 ExperimentFilters: stringset.NewFromSlice( 115 "+"+bb.ExperimentBBCanarySoftware, 116 "-"+bb.ExperimentNonProduction, 117 ), 118 BuildIDHigh: 201, 119 BuildIDLow: 99, 120 DescendantOf: 2, 121 PageSize: 100, 122 PageToken: "", 123 }) 124 }) 125 126 Convey("empty req", func() { 127 So(NewQuery(&pb.SearchBuildsRequest{}), ShouldResemble, &Query{PageSize: 100}) 128 }) 129 130 Convey("empty predict", func() { 131 req := &pb.SearchBuildsRequest{ 132 PageToken: "aa", 133 PageSize: 2, 134 } 135 query := NewQuery(req) 136 137 So(query, ShouldResemble, &Query{ 138 PageSize: 2, 139 PageToken: "aa", 140 }) 141 }) 142 143 Convey("empty identity", func() { 144 req := &pb.SearchBuildsRequest{ 145 Predicate: &pb.BuildPredicate{ 146 CreatedBy: "", 147 }, 148 } 149 query := NewQuery(req) 150 151 So(query.CreatedBy, ShouldEqual, identity.Identity("")) 152 }) 153 154 Convey("invalid create time", func() { 155 req := &pb.SearchBuildsRequest{ 156 Predicate: &pb.BuildPredicate{ 157 CreatedBy: string(identity.AnonymousIdentity), 158 CreateTime: &pb.TimeRange{ 159 StartTime: ×tamppb.Timestamp{Seconds: int64(253402300801)}, 160 }, 161 }, 162 } 163 So(func() { NewQuery(req) }, ShouldPanic) 164 }) 165 }) 166 } 167 168 func TestFixPageSize(t *testing.T) { 169 t.Parallel() 170 171 Convey("normal page size", t, func() { 172 So(fixPageSize(200), ShouldEqual, 200) 173 }) 174 175 Convey("default page size", t, func() { 176 So(fixPageSize(0), ShouldEqual, 100) 177 }) 178 179 Convey("max page size", t, func() { 180 So(fixPageSize(1500), ShouldEqual, 1000) 181 }) 182 } 183 184 func TestMustTimestamp(t *testing.T) { 185 t.Parallel() 186 Convey("normal timestamp", t, func() { 187 res := mustTimestamp(×tamppb.Timestamp{Seconds: 1592701200}) 188 So(res, ShouldEqual, time.Unix(1592701200, 0).UTC()) 189 }) 190 Convey("invalid timestamp", t, func() { 191 So(func() { mustTimestamp(×tamppb.Timestamp{Seconds: 253402300801}) }, ShouldPanic) 192 }) 193 Convey("nil timestamp", t, func() { 194 res := mustTimestamp(nil) 195 So(res.IsZero(), ShouldBeTrue) 196 }) 197 } 198 199 func experiments(canary, experimental bool) (ret []string) { 200 if canary { 201 ret = append(ret, "+"+bb.ExperimentBBCanarySoftware) 202 } else { 203 ret = append(ret, "-"+bb.ExperimentBBCanarySoftware) 204 } 205 206 if experimental { 207 ret = append(ret, "+"+bb.ExperimentNonProduction) 208 } 209 return 210 } 211 212 func TestMainFetchFlow(t *testing.T) { 213 t.Parallel() 214 215 Convey("Fetch", t, func() { 216 ctx := memory.Use(context.Background()) 217 ctx = memlogger.Use(ctx) 218 datastore.GetTestable(ctx).AutoIndex(true) 219 datastore.GetTestable(ctx).Consistent(true) 220 221 So(datastore.Put( 222 ctx, 223 &model.Bucket{ 224 Parent: model.ProjectKey(ctx, "project"), 225 ID: "bucket", 226 Proto: &pb.Bucket{}, 227 }, 228 &model.Builder{ 229 Parent: model.BucketKey(ctx, "project", "bucket"), 230 ID: "builder", 231 Config: &pb.BuilderConfig{Name: "builder"}, 232 }, 233 ), ShouldBeNil) 234 235 query := NewQuery(&pb.SearchBuildsRequest{ 236 Predicate: &pb.BuildPredicate{ 237 Builder: &pb.BuilderID{ 238 Project: "project", 239 Bucket: "bucket", 240 Builder: "builder", 241 }, 242 }, 243 }) 244 245 Convey("No permission for requested bucketId", func() { 246 ctx = auth.WithState(ctx, &authtest.FakeState{ 247 Identity: userID, 248 }) 249 _, err := query.Fetch(ctx) 250 So(err, ShouldHaveAppStatus, codes.NotFound, "not found") 251 }) 252 253 Convey("With read permission", func() { 254 ctx = auth.WithState(ctx, &authtest.FakeState{ 255 Identity: userID, 256 FakeDB: authtest.NewFakeDB( 257 authtest.MockPermission(userID, "project:bucket", bbperms.BuildersList), 258 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsList), 259 ), 260 }) 261 262 Convey("Fetch via TagIndex flow", func() { 263 query.Tags = strpair.ParseMap([]string{"buildset:1"}) 264 actualRsp, err := query.Fetch(ctx) 265 So(err, ShouldBeNil) 266 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{}) 267 }) 268 269 Convey("Fetch via Build flow", func() { 270 So(datastore.Put(ctx, &model.Build{ 271 Proto: &pb.Build{ 272 Id: 1, 273 Builder: &pb.BuilderID{ 274 Project: "project", 275 Bucket: "bucket", 276 Builder: "builder", 277 }, 278 }, 279 BucketID: "project/bucket", 280 BuilderID: "project/bucket/builder", 281 Experiments: experiments(false, false), 282 }), ShouldBeNil) 283 284 query := NewQuery(&pb.SearchBuildsRequest{}) 285 rsp, err := query.Fetch(ctx) 286 So(err, ShouldBeNil) 287 expectedRsp := &pb.SearchBuildsResponse{ 288 Builds: []*pb.Build{ 289 { 290 Id: 1, 291 Builder: &pb.BuilderID{ 292 Project: "project", 293 Bucket: "bucket", 294 Builder: "builder", 295 }, 296 }, 297 }, 298 } 299 So(rsp, ShouldResembleProto, expectedRsp) 300 }) 301 302 Convey("Fallback to fetchOnBuild flow", func() { 303 So(datastore.Put(ctx, &model.TagIndex{ 304 ID: ":10:buildset:1", 305 Incomplete: true, 306 Entries: nil, 307 }), ShouldBeNil) 308 So(datastore.Put(ctx, &model.Build{ 309 Proto: &pb.Build{ 310 Id: 1, 311 Builder: &pb.BuilderID{ 312 Project: "project", 313 Bucket: "bucket", 314 Builder: "builder", 315 }, 316 }, 317 BucketID: "project/bucket", 318 BuilderID: "project/bucket/builder", 319 Tags: []string{"buildset:1"}, 320 Experiments: experiments(false, false), 321 }), ShouldBeNil) 322 323 query.Tags = strpair.ParseMap([]string{"buildset:1"}) 324 actualRsp, err := query.Fetch(ctx) 325 So(err, ShouldBeNil) 326 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{ 327 Builds: []*pb.Build{ 328 { 329 Id: 1, 330 Builder: &pb.BuilderID{ 331 Project: "project", 332 Bucket: "bucket", 333 Builder: "builder", 334 }, 335 Tags: []*pb.StringPair{ 336 { 337 Key: "buildset", 338 Value: "1", 339 }, 340 }, 341 }, 342 }, 343 }) 344 }) 345 }) 346 }) 347 } 348 349 func TestFetchOnBuild(t *testing.T) { 350 t.Parallel() 351 352 Convey("FetchOnBuild", t, func() { 353 ctx := memory.Use(context.Background()) 354 ctx = auth.WithState(ctx, &authtest.FakeState{ 355 Identity: userID, 356 FakeDB: authtest.NewFakeDB( 357 authtest.MockPermission(userID, "project:bucket", bbperms.BuildersList), 358 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsList), 359 ), 360 }) 361 datastore.GetTestable(ctx).AutoIndex(true) 362 datastore.GetTestable(ctx).Consistent(true) 363 364 So(datastore.Put(ctx, &model.Bucket{ 365 ID: "bucket", 366 Parent: model.ProjectKey(ctx, "project"), 367 Proto: &pb.Bucket{}, 368 }), ShouldBeNil) 369 So(datastore.Put(ctx, &model.Build{ 370 ID: 100, 371 Proto: &pb.Build{ 372 Id: 100, 373 Builder: &pb.BuilderID{ 374 Project: "project", 375 Bucket: "bucket", 376 Builder: "builder1", 377 }, 378 Status: pb.Status_SUCCESS, 379 }, 380 Status: pb.Status_SUCCESS, 381 Project: "project", 382 BucketID: "project/bucket", 383 BuilderID: "project/bucket/builder1", 384 Tags: []string{"k1:v1", "k2:v2"}, 385 Experiments: experiments(false, false), 386 }), ShouldBeNil) 387 So(datastore.Put(ctx, &model.Build{ 388 ID: 200, 389 Proto: &pb.Build{ 390 Id: 200, 391 Builder: &pb.BuilderID{ 392 Project: "project", 393 Bucket: "bucket", 394 Builder: "builder2", 395 }, 396 Status: pb.Status_CANCELED, 397 }, 398 Status: pb.Status_CANCELED, 399 Project: "project", 400 BucketID: "project/bucket", 401 BuilderID: "project/bucket/builder2", 402 Experiments: experiments(false, false), 403 }), ShouldBeNil) 404 405 Convey("found by builder", func() { 406 req := &pb.SearchBuildsRequest{ 407 Predicate: &pb.BuildPredicate{ 408 Builder: &pb.BuilderID{ 409 Project: "project", 410 Bucket: "bucket", 411 Builder: "builder1", 412 }, 413 }, 414 } 415 query := NewQuery(req) 416 actualRsp, err := query.fetchOnBuild(ctx) 417 expectedRsp := &pb.SearchBuildsResponse{ 418 Builds: []*pb.Build{ 419 { 420 Id: 100, 421 Builder: &pb.BuilderID{ 422 Project: "project", 423 Bucket: "bucket", 424 Builder: "builder1", 425 }, 426 Tags: []*pb.StringPair{ 427 {Key: "k1", Value: "v1"}, 428 {Key: "k2", Value: "v2"}, 429 }, 430 Status: pb.Status_SUCCESS, 431 }, 432 }, 433 } 434 435 So(err, ShouldBeNil) 436 So(actualRsp, ShouldResembleProto, expectedRsp) 437 }) 438 Convey("found by tag", func() { 439 req := &pb.SearchBuildsRequest{ 440 Predicate: &pb.BuildPredicate{ 441 Status: pb.Status_SUCCESS, 442 Tags: []*pb.StringPair{ 443 {Key: "k1", Value: "v1"}, 444 {Key: "k2", Value: "v2"}, 445 }, 446 }, 447 } 448 query := NewQuery(req) 449 actualRsp, err := query.fetchOnBuild(ctx) 450 expectedRsp := &pb.SearchBuildsResponse{ 451 Builds: []*pb.Build{ 452 { 453 Id: 100, 454 Builder: &pb.BuilderID{ 455 Project: "project", 456 Bucket: "bucket", 457 Builder: "builder1", 458 }, 459 Tags: []*pb.StringPair{ 460 {Key: "k1", Value: "v1"}, 461 {Key: "k2", Value: "v2"}, 462 }, 463 Status: pb.Status_SUCCESS, 464 }, 465 }, 466 } 467 468 So(err, ShouldBeNil) 469 So(actualRsp, ShouldResembleProto, expectedRsp) 470 }) 471 Convey("found by status", func() { 472 req := &pb.SearchBuildsRequest{ 473 Predicate: &pb.BuildPredicate{ 474 Status: pb.Status_SUCCESS, 475 }, 476 } 477 query := NewQuery(req) 478 actualRsp, err := query.fetchOnBuild(ctx) 479 expectedRsp := &pb.SearchBuildsResponse{ 480 Builds: []*pb.Build{ 481 { 482 Id: 100, 483 Builder: &pb.BuilderID{ 484 Project: "project", 485 Bucket: "bucket", 486 Builder: "builder1", 487 }, 488 Tags: []*pb.StringPair{ 489 {Key: "k1", Value: "v1"}, 490 {Key: "k2", Value: "v2"}, 491 }, 492 Status: pb.Status_SUCCESS, 493 }, 494 }, 495 } 496 497 So(err, ShouldBeNil) 498 So(actualRsp, ShouldResembleProto, expectedRsp) 499 }) 500 Convey("found by build range", func() { 501 req := &pb.SearchBuildsRequest{ 502 Predicate: &pb.BuildPredicate{ 503 Build: &pb.BuildRange{ 504 StartBuildId: 199, 505 EndBuildId: 99, 506 }, 507 }, 508 } 509 query := NewQuery(req) 510 actualRsp, err := query.fetchOnBuild(ctx) 511 expectedRsp := &pb.SearchBuildsResponse{ 512 Builds: []*pb.Build{ 513 { 514 Id: 100, 515 Builder: &pb.BuilderID{ 516 Project: "project", 517 Bucket: "bucket", 518 Builder: "builder1", 519 }, 520 Tags: []*pb.StringPair{ 521 {Key: "k1", Value: "v1"}, 522 {Key: "k2", Value: "v2"}, 523 }, 524 Status: pb.Status_SUCCESS, 525 }, 526 }, 527 } 528 529 So(err, ShouldBeNil) 530 So(actualRsp, ShouldResembleProto, expectedRsp) 531 }) 532 Convey("found by create time", func() { 533 So(datastore.Put(ctx, &model.Build{ 534 ID: 8764414515958775808, 535 Proto: &pb.Build{ 536 Builder: &pb.BuilderID{ 537 Project: "project", 538 Bucket: "bucket", 539 Builder: "builder5808", 540 }, 541 Id: 8764414515958775808, 542 }, 543 BucketID: "project/bucket", 544 Experiments: experiments(false, false), 545 }), ShouldBeNil) 546 req := &pb.SearchBuildsRequest{ 547 Predicate: &pb.BuildPredicate{ 548 CreateTime: &pb.TimeRange{ 549 StartTime: ×tamppb.Timestamp{Seconds: 1592701200}, 550 EndTime: ×tamppb.Timestamp{Seconds: 1700000000}, 551 }, 552 }, 553 } 554 query := NewQuery(req) 555 actualRsp, err := query.fetchOnBuild(ctx) 556 expectedRsp := &pb.SearchBuildsResponse{ 557 Builds: []*pb.Build{ 558 { 559 Id: 8764414515958775808, 560 Builder: &pb.BuilderID{ 561 Project: "project", 562 Bucket: "bucket", 563 Builder: "builder5808", 564 }, 565 }, 566 }, 567 } 568 569 So(err, ShouldBeNil) 570 So(actualRsp, ShouldResembleProto, expectedRsp) 571 }) 572 Convey("found by created_by", func() { 573 So(datastore.Put(ctx, &model.Build{ 574 ID: 1111, 575 Proto: &pb.Build{ 576 Id: 1111, 577 CreatedBy: "project:infra", 578 Builder: &pb.BuilderID{ 579 Project: "project", 580 Bucket: "bucket", 581 Builder: "builder1111", 582 }, 583 }, 584 CreatedBy: "project:infra", 585 BucketID: "project/bucket", 586 Experiments: experiments(false, false), 587 }), ShouldBeNil) 588 req := &pb.SearchBuildsRequest{ 589 Predicate: &pb.BuildPredicate{ 590 CreatedBy: "project:infra", 591 }, 592 } 593 query := NewQuery(req) 594 actualRsp, err := query.fetchOnBuild(ctx) 595 expectedRsp := &pb.SearchBuildsResponse{ 596 Builds: []*pb.Build{ 597 { 598 Id: 1111, 599 CreatedBy: "project:infra", 600 Builder: &pb.BuilderID{ 601 Project: "project", 602 Bucket: "bucket", 603 Builder: "builder1111", 604 }, 605 }, 606 }, 607 } 608 609 So(err, ShouldBeNil) 610 So(actualRsp, ShouldResembleProto, expectedRsp) 611 }) 612 Convey("found by ENDED_MASK", func() { 613 So(datastore.Put(ctx, &model.Build{ 614 ID: 300, 615 Proto: &pb.Build{ 616 Id: 300, 617 Builder: &pb.BuilderID{ 618 Project: "project", 619 Bucket: "bucket", 620 Builder: "builder3", 621 }, 622 Status: pb.Status_STARTED, 623 }, 624 Project: "project", 625 BucketID: "project/bucket", 626 BuilderID: "project/bucket/builder3", 627 Status: pb.Status_STARTED, 628 Experiments: experiments(false, false), 629 }), ShouldBeNil) 630 631 req := &pb.SearchBuildsRequest{ 632 Predicate: &pb.BuildPredicate{ 633 Status: pb.Status_ENDED_MASK, 634 }, 635 } 636 query := NewQuery(req) 637 actualRsp, err := query.fetchOnBuild(ctx) 638 expectedRsp := &pb.SearchBuildsResponse{ 639 Builds: []*pb.Build{ 640 { 641 Id: 100, 642 Builder: &pb.BuilderID{ 643 Project: "project", 644 Bucket: "bucket", 645 Builder: "builder1", 646 }, 647 Tags: []*pb.StringPair{ 648 {Key: "k1", Value: "v1"}, 649 {Key: "k2", Value: "v2"}, 650 }, 651 Status: pb.Status_SUCCESS, 652 }, 653 { 654 Id: 200, 655 Builder: &pb.BuilderID{ 656 Project: "project", 657 Bucket: "bucket", 658 Builder: "builder2", 659 }, 660 Status: pb.Status_CANCELED, 661 }, 662 }, 663 } 664 665 So(err, ShouldBeNil) 666 So(actualRsp, ShouldResembleProto, expectedRsp) 667 }) 668 Convey("found by canary", func() { 669 req := &pb.SearchBuildsRequest{ 670 Predicate: &pb.BuildPredicate{ 671 Canary: pb.Trinary_YES, 672 }, 673 } 674 So(datastore.Put(ctx, &model.Build{ 675 ID: 321, 676 Proto: &pb.Build{ 677 Id: 321, 678 Builder: &pb.BuilderID{ 679 Project: "project", 680 Bucket: "bucket", 681 Builder: "builder321", 682 }, 683 Canary: true, 684 }, 685 Project: "project", 686 BucketID: "project/bucket", 687 BuilderID: "project/bucket/builder321", 688 Experiments: experiments(true, false), 689 }), ShouldBeNil) 690 query := NewQuery(req) 691 actualRsp, err := query.fetchOnBuild(ctx) 692 expectedRsp := &pb.SearchBuildsResponse{ 693 Builds: []*pb.Build{ 694 { 695 Id: 321, 696 Builder: &pb.BuilderID{ 697 Project: "project", 698 Bucket: "bucket", 699 Builder: "builder321", 700 }, 701 Canary: true, 702 }, 703 }, 704 } 705 706 So(err, ShouldBeNil) 707 So(actualRsp, ShouldResembleProto, expectedRsp) 708 }) 709 Convey("found only experimental", func() { 710 req := &pb.SearchBuildsRequest{ 711 Predicate: &pb.BuildPredicate{ 712 Experiments: []string{"+" + bb.ExperimentNonProduction}, 713 }, 714 } 715 So(datastore.Put(ctx, &model.Build{ 716 ID: 321, 717 Proto: &pb.Build{ 718 Id: 321, 719 Builder: &pb.BuilderID{ 720 Project: "project", 721 Bucket: "bucket", 722 Builder: "builder321", 723 }, 724 Input: &pb.Build_Input{Experimental: true}, 725 }, 726 Project: "project", 727 BucketID: "project/bucket", 728 BuilderID: "project/bucket/builder321", 729 Experiments: experiments(false, true), 730 }), ShouldBeNil) 731 So(datastore.Put(ctx, &model.Build{ 732 ID: 123, 733 Proto: &pb.Build{ 734 Id: 123, 735 Builder: &pb.BuilderID{ 736 Project: "project", 737 Bucket: "bucket", 738 Builder: "builder321", 739 }, 740 }, 741 Project: "project", 742 BucketID: "project/bucket", 743 BuilderID: "project/bucket/builder123", 744 Experiments: experiments(false, false), 745 }), ShouldBeNil) 746 query := NewQuery(req) 747 actualRsp, err := query.fetchOnBuild(ctx) 748 expectedRsp := &pb.SearchBuildsResponse{ 749 Builds: []*pb.Build{ 750 { 751 Id: 321, 752 Builder: &pb.BuilderID{ 753 Project: "project", 754 Bucket: "bucket", 755 Builder: "builder321", 756 }, 757 Input: &pb.Build_Input{Experimental: true}, 758 }, 759 }, 760 } 761 762 So(err, ShouldBeNil) 763 So(actualRsp, ShouldResembleProto, expectedRsp) 764 }) 765 Convey("found non experimental", func() { 766 req := &pb.SearchBuildsRequest{ 767 Predicate: &pb.BuildPredicate{ 768 Experiments: []string{"-" + bb.ExperimentNonProduction}, 769 }, 770 } 771 So(datastore.Put(ctx, &model.Build{ 772 ID: 321, 773 Proto: &pb.Build{ 774 Id: 321, 775 Builder: &pb.BuilderID{ 776 Project: "project", 777 Bucket: "bucket", 778 Builder: "builder321", 779 }, 780 Input: &pb.Build_Input{Experimental: true}, 781 }, 782 Project: "project", 783 BucketID: "project/bucket", 784 BuilderID: "project/bucket/builder321", 785 Experiments: experiments(false, true), 786 }), ShouldBeNil) 787 So(datastore.Put(ctx, &model.Build{ 788 ID: 123, 789 Proto: &pb.Build{ 790 Id: 123, 791 Builder: &pb.BuilderID{ 792 Project: "project", 793 Bucket: "bucket", 794 Builder: "builder123", 795 }, 796 }, 797 Project: "project", 798 BucketID: "project/bucket", 799 BuilderID: "project/bucket/builder123", 800 Experiments: experiments(false, false), 801 }), ShouldBeNil) 802 query := NewQuery(req) 803 actualRsp, err := query.fetchOnBuild(ctx) 804 expectedRsp := &pb.SearchBuildsResponse{ 805 Builds: []*pb.Build{ 806 { 807 Id: 100, 808 Builder: &pb.BuilderID{ 809 Project: "project", 810 Bucket: "bucket", 811 Builder: "builder1", 812 }, 813 Tags: []*pb.StringPair{ 814 {Key: "k1", Value: "v1"}, 815 {Key: "k2", Value: "v2"}, 816 }, 817 Status: pb.Status_SUCCESS, 818 }, 819 { 820 Id: 123, 821 Builder: &pb.BuilderID{ 822 Project: "project", 823 Bucket: "bucket", 824 Builder: "builder123", 825 }, 826 }, 827 { 828 Id: 200, 829 Builder: &pb.BuilderID{ 830 Project: "project", 831 Bucket: "bucket", 832 Builder: "builder2", 833 }, 834 Status: pb.Status_CANCELED, 835 }, 836 }, 837 } 838 839 So(err, ShouldBeNil) 840 So(actualRsp, ShouldResembleProto, expectedRsp) 841 }) 842 Convey("found by ancestors", func() { 843 bIDs := func(rsp *pb.SearchBuildsResponse) []int { 844 ids := make([]int, 0, len(rsp.Builds)) 845 for _, b := range rsp.Builds { 846 ids = append(ids, int(b.Id)) 847 } 848 sort.Ints(ids) 849 return ids 850 } 851 So(datastore.Put(ctx, &model.Build{ 852 ID: 1, 853 Proto: &pb.Build{ 854 Id: 1, 855 Builder: &pb.BuilderID{ 856 Project: "project", 857 Bucket: "bucket", 858 Builder: "builder", 859 }, 860 }, 861 }), ShouldBeNil) 862 So(datastore.Put(ctx, &model.Build{ 863 ID: 2, 864 Proto: &pb.Build{ 865 Id: 2, 866 Builder: &pb.BuilderID{ 867 Project: "project", 868 Bucket: "bucket", 869 Builder: "builder", 870 }, 871 AncestorIds: []int64{1}, 872 }, 873 }), ShouldBeNil) 874 So(datastore.Put(ctx, &model.Build{ 875 ID: 3, 876 Proto: &pb.Build{ 877 Id: 3, 878 Builder: &pb.BuilderID{ 879 Project: "project", 880 Bucket: "bucket", 881 Builder: "builder", 882 }, 883 AncestorIds: []int64{1}, 884 }, 885 }), ShouldBeNil) 886 So(datastore.Put(ctx, &model.Build{ 887 ID: 4, 888 Proto: &pb.Build{ 889 Id: 4, 890 Builder: &pb.BuilderID{ 891 Project: "project", 892 Bucket: "bucket", 893 Builder: "builder", 894 }, 895 AncestorIds: []int64{1, 2}, 896 }, 897 }), ShouldBeNil) 898 Convey("by ancestor_ids", func() { 899 req := &pb.SearchBuildsRequest{ 900 Predicate: &pb.BuildPredicate{ 901 DescendantOf: 1, 902 }, 903 } 904 query := NewQuery(req) 905 actualRsp, err := query.fetchOnBuild(ctx) 906 So(err, ShouldBeNil) 907 So(bIDs(actualRsp), ShouldResemble, []int{2, 3, 4}) 908 }) 909 Convey("by parent_id", func() { 910 req := &pb.SearchBuildsRequest{ 911 Predicate: &pb.BuildPredicate{ 912 ChildOf: 1, 913 }, 914 } 915 query := NewQuery(req) 916 actualRsp, err := query.fetchOnBuild(ctx) 917 So(err, ShouldBeNil) 918 So(bIDs(actualRsp), ShouldResemble, []int{2, 3}) 919 }) 920 }) 921 Convey("empty request", func() { 922 req := &pb.SearchBuildsRequest{ 923 Predicate: &pb.BuildPredicate{}, 924 } 925 query := NewQuery(req) 926 actualRsp, err := query.fetchOnBuild(ctx) 927 expectedRsp := &pb.SearchBuildsResponse{ 928 Builds: []*pb.Build{ 929 { 930 Id: 100, 931 Builder: &pb.BuilderID{ 932 Project: "project", 933 Bucket: "bucket", 934 Builder: "builder1", 935 }, 936 Tags: []*pb.StringPair{ 937 {Key: "k1", Value: "v1"}, 938 {Key: "k2", Value: "v2"}, 939 }, 940 Status: pb.Status_SUCCESS, 941 }, 942 { 943 Id: 200, 944 Builder: &pb.BuilderID{ 945 Project: "project", 946 Bucket: "bucket", 947 Builder: "builder2", 948 }, 949 Status: pb.Status_CANCELED, 950 }, 951 }, 952 } 953 954 So(err, ShouldBeNil) 955 So(actualRsp, ShouldResembleProto, expectedRsp) 956 }) 957 Convey("pagination", func() { 958 So(datastore.Put(ctx, &model.Build{ 959 ID: 300, 960 Proto: &pb.Build{ 961 Id: 300, 962 Builder: &pb.BuilderID{ 963 Project: "project", 964 Bucket: "bucket", 965 Builder: "builder3", 966 }, 967 }, 968 Project: "project", 969 BucketID: "project/bucket", 970 BuilderID: "project/bucket/builder3", 971 Experiments: experiments(false, false), 972 }), ShouldBeNil) 973 974 // this build can be fetched from db but not accessible by the user. 975 So(datastore.Put(ctx, &model.Build{ 976 ID: 400, 977 Proto: &pb.Build{ 978 Id: 400, 979 Builder: &pb.BuilderID{ 980 Project: "project_no_access", 981 Bucket: "bucket", 982 Builder: "builder", 983 }, 984 }, 985 Project: "project_no_access", 986 BucketID: "project_no_access/bucket", 987 BuilderID: "project_no_access/bucket/builder", 988 Experiments: experiments(false, false), 989 }), ShouldBeNil) 990 991 req := &pb.SearchBuildsRequest{ 992 PageSize: 2, 993 } 994 995 // fetch 1st page. 996 query := NewQuery(req) 997 actualRsp, err := query.fetchOnBuild(ctx) 998 expectedBuilds := []*pb.Build{ 999 { 1000 Id: 100, 1001 Builder: &pb.BuilderID{ 1002 Project: "project", 1003 Bucket: "bucket", 1004 Builder: "builder1", 1005 }, 1006 Tags: []*pb.StringPair{ 1007 {Key: "k1", Value: "v1"}, 1008 {Key: "k2", Value: "v2"}, 1009 }, 1010 Status: pb.Status_SUCCESS, 1011 }, 1012 { 1013 Id: 200, 1014 Builder: &pb.BuilderID{ 1015 Project: "project", 1016 Bucket: "bucket", 1017 Builder: "builder2", 1018 }, 1019 Status: pb.Status_CANCELED, 1020 }, 1021 } 1022 1023 So(err, ShouldBeNil) 1024 So(actualRsp.Builds, ShouldResembleProto, expectedBuilds) 1025 So(actualRsp.NextPageToken, ShouldEqual, "id>200") 1026 1027 // fetch the following page (response should have a build with the ID - 400). 1028 req.PageToken = actualRsp.NextPageToken 1029 query = NewQuery(req) 1030 actualRsp, err = query.fetchOnBuild(ctx) 1031 expectedBuilds = []*pb.Build{ 1032 { 1033 Id: 300, 1034 Builder: &pb.BuilderID{ 1035 Project: "project", 1036 Bucket: "bucket", 1037 Builder: "builder3", 1038 }, 1039 }, 1040 } 1041 1042 So(err, ShouldBeNil) 1043 So(actualRsp.Builds, ShouldResembleProto, expectedBuilds) 1044 So(actualRsp.NextPageToken, ShouldBeEmpty) 1045 }) 1046 Convey("found by start build id and pagination", func() { 1047 req := &pb.SearchBuildsRequest{ 1048 Predicate: &pb.BuildPredicate{ 1049 Build: &pb.BuildRange{ 1050 StartBuildId: 199, 1051 }, 1052 }, 1053 PageToken: "id>199", 1054 } 1055 query := NewQuery(req) 1056 actualRsp, err := query.fetchOnBuild(ctx) 1057 expectedRsp := &pb.SearchBuildsResponse{} 1058 1059 So(err, ShouldBeNil) 1060 So(actualRsp, ShouldResembleProto, expectedRsp) 1061 }) 1062 }) 1063 } 1064 1065 func TestIndexedTags(t *testing.T) { 1066 t.Parallel() 1067 1068 Convey("tags", t, func() { 1069 tags := strpair.Map{ 1070 "a": []string{"b"}, 1071 "buildset": []string{"b1"}, 1072 } 1073 result := IndexedTags(tags) 1074 So(result, ShouldResemble, []string{"buildset:b1"}) 1075 }) 1076 1077 Convey("duplicate tags", t, func() { 1078 tags := strpair.Map{ 1079 "buildset": []string{"b1", "b1"}, 1080 "build_address": []string{"address"}, 1081 } 1082 result := IndexedTags(tags) 1083 So(result, ShouldResemble, []string{"build_address:address", "buildset:b1"}) 1084 }) 1085 1086 Convey("empty tags", t, func() { 1087 tags := strpair.Map{} 1088 result := IndexedTags(tags) 1089 So(result, ShouldResemble, []string{}) 1090 }) 1091 } 1092 1093 func TestUpdateTagIndex(t *testing.T) { 1094 t.Parallel() 1095 1096 Convey("UpdateTagIndex", t, func() { 1097 ctx, _ := testclock.UseTime(memory.Use(context.Background()), testclock.TestRecentTimeUTC) 1098 datastore.GetTestable(ctx).AutoIndex(true) 1099 datastore.GetTestable(ctx).Consistent(true) 1100 1101 builds := []*model.Build{ 1102 { 1103 ID: 1, 1104 Proto: &pb.Build{ 1105 Builder: &pb.BuilderID{ 1106 Project: "project", 1107 Bucket: "bucket", 1108 Builder: "builder", 1109 }, 1110 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1111 }, 1112 Tags: []string{ 1113 "a:b", 1114 "buildset:b1", 1115 }, 1116 Experiments: experiments(false, false), 1117 }, 1118 { 1119 ID: 2, 1120 Proto: &pb.Build{ 1121 Builder: &pb.BuilderID{ 1122 Project: "project", 1123 Bucket: "bucket", 1124 Builder: "builder", 1125 }, 1126 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1127 }, 1128 Tags: []string{ 1129 "a:b", 1130 "build_address:address", 1131 "buildset:b1", 1132 }, 1133 Experiments: experiments(false, false), 1134 }, 1135 } 1136 So(UpdateTagIndex(ctx, builds), ShouldBeNil) 1137 1138 idx, err := model.SearchTagIndex(ctx, "a", "b") 1139 So(err, ShouldBeNil) 1140 So(idx, ShouldBeNil) 1141 1142 idx, err = model.SearchTagIndex(ctx, "buildset", "b1") 1143 So(err, ShouldBeNil) 1144 So(idx, ShouldResemble, []*model.TagIndexEntry{ 1145 { 1146 BuildID: int64(1), 1147 BucketID: "project/bucket", 1148 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 1149 }, 1150 { 1151 BuildID: int64(2), 1152 BucketID: "project/bucket", 1153 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 1154 }, 1155 }) 1156 1157 idx, err = model.SearchTagIndex(ctx, "build_address", "address") 1158 So(err, ShouldBeNil) 1159 So(idx, ShouldResemble, []*model.TagIndexEntry{ 1160 { 1161 BuildID: int64(2), 1162 BucketID: "project/bucket", 1163 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 1164 }, 1165 }) 1166 }) 1167 } 1168 1169 func TestFetchOnTagIndex(t *testing.T) { 1170 t.Parallel() 1171 1172 Convey("FetchOnTagIndex", t, func() { 1173 ctx := memory.Use(context.Background()) 1174 ctx = auth.WithState(ctx, &authtest.FakeState{ 1175 Identity: userID, 1176 FakeDB: authtest.NewFakeDB( 1177 authtest.MockPermission(userID, "project:bucket", bbperms.BuildersList), 1178 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsList), 1179 ), 1180 }) 1181 datastore.GetTestable(ctx).AutoIndex(true) 1182 datastore.GetTestable(ctx).Consistent(true) 1183 1184 So(datastore.Put(ctx, &model.Bucket{ 1185 ID: "bucket", 1186 Parent: model.ProjectKey(ctx, "project"), 1187 Proto: &pb.Bucket{}, 1188 }), ShouldBeNil) 1189 So(datastore.Put(ctx, &model.Build{ 1190 ID: 100, 1191 Proto: &pb.Build{ 1192 Id: 100, 1193 Builder: &pb.BuilderID{ 1194 Project: "project", 1195 Bucket: "bucket", 1196 Builder: "builder1", 1197 }, 1198 Status: pb.Status_SUCCESS, 1199 }, 1200 Status: pb.Status_SUCCESS, 1201 Project: "project", 1202 BucketID: "project/bucket", 1203 BuilderID: "project/bucket/builder1", 1204 Tags: []string{"buildset:commit/git/abcd"}, 1205 Experiments: experiments(false, false), 1206 }), ShouldBeNil) 1207 So(datastore.Put(ctx, &model.Build{ 1208 ID: 200, 1209 Proto: &pb.Build{ 1210 Id: 200, 1211 Builder: &pb.BuilderID{ 1212 Project: "project", 1213 Bucket: "bucket", 1214 Builder: "builder2", 1215 }, 1216 Status: pb.Status_CANCELED, 1217 }, 1218 Status: pb.Status_CANCELED, 1219 Project: "project", 1220 BucketID: "project/bucket", 1221 BuilderID: "project/bucket/builder2", 1222 Tags: []string{"buildset:commit/git/abcd"}, 1223 // legacy; no Experiments, assumed to be prod 1224 }), ShouldBeNil) 1225 So(datastore.Put(ctx, &model.Build{ 1226 ID: 300, 1227 Proto: &pb.Build{ 1228 Id: 300, 1229 Builder: &pb.BuilderID{ 1230 Project: "project", 1231 Bucket: "bucket", 1232 Builder: "builder3", 1233 }, 1234 Status: pb.Status_CANCELED, 1235 Input: &pb.Build_Input{Experimental: true}, 1236 }, 1237 Status: pb.Status_CANCELED, 1238 Project: "project", 1239 BucketID: "project/bucket", 1240 BuilderID: "project/bucket/builder3", 1241 Tags: []string{"buildset:commit/git/abcd"}, 1242 Experiments: experiments(false, true), 1243 }), ShouldBeNil) 1244 So(datastore.Put(ctx, &model.TagIndex{ 1245 ID: ":2:buildset:commit/git/abcd", 1246 Entries: []model.TagIndexEntry{ 1247 { 1248 BuildID: 100, 1249 BucketID: "project/bucket", 1250 }, 1251 }, 1252 }), ShouldBeNil) 1253 So(datastore.Put(ctx, &model.TagIndex{ 1254 ID: ":3:buildset:commit/git/abcd", 1255 Entries: []model.TagIndexEntry{ 1256 { 1257 BuildID: 200, 1258 BucketID: "project/bucket", 1259 }, 1260 { 1261 BuildID: 300, 1262 BucketID: "project/bucket", 1263 }, 1264 }, 1265 }), ShouldBeNil) 1266 req := &pb.SearchBuildsRequest{ 1267 Predicate: &pb.BuildPredicate{ 1268 Tags: []*pb.StringPair{ 1269 {Key: "buildset", Value: "commit/git/abcd"}, 1270 }, 1271 }, 1272 } 1273 Convey("filter only by an indexed tag", func() { 1274 query := NewQuery(req) 1275 actualRsp, err := query.fetchOnTagIndex(ctx) 1276 expectedRsp := &pb.SearchBuildsResponse{ 1277 Builds: []*pb.Build{ 1278 { 1279 Id: 100, 1280 Builder: &pb.BuilderID{ 1281 Project: "project", 1282 Bucket: "bucket", 1283 Builder: "builder1", 1284 }, 1285 Tags: []*pb.StringPair{ 1286 {Key: "buildset", Value: "commit/git/abcd"}, 1287 }, 1288 Status: pb.Status_SUCCESS, 1289 }, 1290 { 1291 Id: 200, 1292 Builder: &pb.BuilderID{ 1293 Project: "project", 1294 Bucket: "bucket", 1295 Builder: "builder2", 1296 }, 1297 Tags: []*pb.StringPair{ 1298 {Key: "buildset", Value: "commit/git/abcd"}, 1299 }, 1300 Status: pb.Status_CANCELED, 1301 }, 1302 }, 1303 } 1304 1305 So(err, ShouldBeNil) 1306 So(actualRsp, ShouldResembleProto, expectedRsp) 1307 }) 1308 1309 Convey("filter by status", func() { 1310 req.Predicate.Status = pb.Status_CANCELED 1311 query := NewQuery(req) 1312 actualRsp, err := query.fetchOnTagIndex(ctx) 1313 expectedRsp := &pb.SearchBuildsResponse{ 1314 Builds: []*pb.Build{ 1315 { 1316 Id: 200, 1317 Builder: &pb.BuilderID{ 1318 Project: "project", 1319 Bucket: "bucket", 1320 Builder: "builder2", 1321 }, 1322 Tags: []*pb.StringPair{ 1323 {Key: "buildset", Value: "commit/git/abcd"}, 1324 }, 1325 Status: pb.Status_CANCELED, 1326 }, 1327 }, 1328 } 1329 So(err, ShouldBeNil) 1330 So(actualRsp, ShouldResembleProto, expectedRsp) 1331 }) 1332 Convey("filter by ENDED_MASK", func() { 1333 So(datastore.Put(ctx, &model.Build{ 1334 ID: 999, 1335 Proto: &pb.Build{ 1336 Id: 999, 1337 Status: pb.Status_STARTED, 1338 Builder: &pb.BuilderID{ 1339 Project: "project", 1340 Bucket: "bucket", 1341 Builder: "builder999", 1342 }, 1343 }, 1344 Project: "project", 1345 BucketID: "project/bucket", 1346 BuilderID: "project/bucket/builder999", 1347 Status: pb.Status_STARTED, 1348 Tags: []string{"buildset:commit/git/abcd"}, 1349 Experiments: experiments(false, false), 1350 }), ShouldBeNil) 1351 So(datastore.Put(ctx, &model.TagIndex{ 1352 ID: ":4:buildset:commit/git/abcd", 1353 Entries: []model.TagIndexEntry{ 1354 { 1355 BuildID: 999, 1356 BucketID: "project/bucket", 1357 }, 1358 }, 1359 }), ShouldBeNil) 1360 req.Predicate.Status = pb.Status_ENDED_MASK 1361 query := NewQuery(req) 1362 actualRsp, err := query.fetchOnTagIndex(ctx) 1363 expectedRsp := &pb.SearchBuildsResponse{ 1364 Builds: []*pb.Build{ 1365 { 1366 Id: 100, 1367 Builder: &pb.BuilderID{ 1368 Project: "project", 1369 Bucket: "bucket", 1370 Builder: "builder1", 1371 }, 1372 Tags: []*pb.StringPair{ 1373 {Key: "buildset", Value: "commit/git/abcd"}, 1374 }, 1375 Status: pb.Status_SUCCESS, 1376 }, 1377 { 1378 Id: 200, 1379 Builder: &pb.BuilderID{ 1380 Project: "project", 1381 Bucket: "bucket", 1382 Builder: "builder2", 1383 }, 1384 Tags: []*pb.StringPair{ 1385 {Key: "buildset", Value: "commit/git/abcd"}, 1386 }, 1387 Status: pb.Status_CANCELED, 1388 }, 1389 }, 1390 } 1391 So(err, ShouldBeNil) 1392 So(actualRsp, ShouldResembleProto, expectedRsp) 1393 }) 1394 Convey("filter by an indexed tag and a normal tag", func() { 1395 req.Predicate.Tags = append(req.Predicate.Tags, &pb.StringPair{Key: "k1", Value: "v1"}) 1396 query := NewQuery(req) 1397 actualRsp, err := query.fetchOnTagIndex(ctx) 1398 So(err, ShouldBeNil) 1399 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{}) 1400 }) 1401 Convey("filter by build range", func() { 1402 req.Predicate.Build = &pb.BuildRange{ 1403 StartBuildId: 199, 1404 EndBuildId: 99, 1405 } 1406 query := NewQuery(req) 1407 actualRsp, err := query.fetchOnTagIndex(ctx) 1408 expectedRsp := &pb.SearchBuildsResponse{ 1409 Builds: []*pb.Build{ 1410 { 1411 Id: 100, 1412 Builder: &pb.BuilderID{ 1413 Project: "project", 1414 Bucket: "bucket", 1415 Builder: "builder1", 1416 }, 1417 Tags: []*pb.StringPair{ 1418 {Key: "buildset", Value: "commit/git/abcd"}, 1419 }, 1420 Status: pb.Status_SUCCESS, 1421 }, 1422 }, 1423 } 1424 1425 So(err, ShouldBeNil) 1426 So(actualRsp, ShouldResembleProto, expectedRsp) 1427 }) 1428 Convey("filter by created_by", func() { 1429 req.Predicate.CreatedBy = "project:infra" 1430 query := NewQuery(req) 1431 actualRsp, err := query.fetchOnTagIndex(ctx) 1432 So(err, ShouldBeNil) 1433 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{}) 1434 }) 1435 Convey("filter by canary", func() { 1436 req.Predicate.Canary = pb.Trinary_YES 1437 query := NewQuery(req) 1438 actualRsp, err := query.fetchOnTagIndex(ctx) 1439 So(err, ShouldBeNil) 1440 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{}) 1441 }) 1442 Convey("filter by IncludeExperimental", func() { 1443 req.Predicate.IncludeExperimental = true 1444 query := NewQuery(req) 1445 actualRsp, err := query.fetchOnTagIndex(ctx) 1446 expectedRsp := &pb.SearchBuildsResponse{ 1447 Builds: []*pb.Build{ 1448 { 1449 Id: 100, 1450 Builder: &pb.BuilderID{ 1451 Project: "project", 1452 Bucket: "bucket", 1453 Builder: "builder1", 1454 }, 1455 Tags: []*pb.StringPair{ 1456 {Key: "buildset", Value: "commit/git/abcd"}, 1457 }, 1458 Status: pb.Status_SUCCESS, 1459 }, 1460 { 1461 Id: 200, 1462 Builder: &pb.BuilderID{ 1463 Project: "project", 1464 Bucket: "bucket", 1465 Builder: "builder2", 1466 }, 1467 Tags: []*pb.StringPair{ 1468 {Key: "buildset", Value: "commit/git/abcd"}, 1469 }, 1470 Status: pb.Status_CANCELED, 1471 }, 1472 { 1473 Id: 300, 1474 Builder: &pb.BuilderID{ 1475 Project: "project", 1476 Bucket: "bucket", 1477 Builder: "builder3", 1478 }, 1479 Tags: []*pb.StringPair{ 1480 {Key: "buildset", Value: "commit/git/abcd"}, 1481 }, 1482 Input: &pb.Build_Input{Experimental: true}, 1483 Status: pb.Status_CANCELED, 1484 }, 1485 }, 1486 } 1487 1488 So(err, ShouldBeNil) 1489 So(actualRsp, ShouldResembleProto, expectedRsp) 1490 }) 1491 Convey("pagination", func() { 1492 req.PageSize = 1 1493 query := NewQuery(req) 1494 actualRsp, err := query.fetchOnTagIndex(ctx) 1495 expectedRsp := &pb.SearchBuildsResponse{ 1496 Builds: []*pb.Build{ 1497 { 1498 Id: 100, 1499 Builder: &pb.BuilderID{ 1500 Project: "project", 1501 Bucket: "bucket", 1502 Builder: "builder1", 1503 }, 1504 Tags: []*pb.StringPair{ 1505 {Key: "buildset", Value: "commit/git/abcd"}, 1506 }, 1507 Status: pb.Status_SUCCESS, 1508 }, 1509 }, 1510 NextPageToken: "id>100", 1511 } 1512 1513 So(err, ShouldBeNil) 1514 So(actualRsp, ShouldResembleProto, expectedRsp) 1515 }) 1516 Convey("No permission on requested buckets", func() { 1517 ctx = auth.WithState(ctx, &authtest.FakeState{ 1518 Identity: "user:none", 1519 }) 1520 query := NewQuery(req) 1521 actualRsp, err := query.fetchOnTagIndex(ctx) 1522 1523 So(err, ShouldBeNil) 1524 So(actualRsp, ShouldResembleProto, &pb.SearchBuildsResponse{}) 1525 }) 1526 }) 1527 } 1528 1529 func TestMinHeap(t *testing.T) { 1530 t.Parallel() 1531 1532 Convey("minHeap", t, func() { 1533 h := &minHeap{{BuildID: 2}, {BuildID: 1}, {BuildID: 5}} 1534 1535 heap.Init(h) 1536 heap.Push(h, &model.TagIndexEntry{BuildID: 3}) 1537 var res []int64 1538 for h.Len() > 0 { 1539 res = append(res, heap.Pop(h).(*model.TagIndexEntry).BuildID) 1540 } 1541 So(res, ShouldResemble, []int64{1, 2, 3, 5}) 1542 }) 1543 1544 }