go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/tasks/notification_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 tasks 16 17 import ( 18 "context" 19 "sort" 20 "testing" 21 22 "google.golang.org/protobuf/encoding/protojson" 23 "google.golang.org/protobuf/proto" 24 "google.golang.org/protobuf/types/known/structpb" 25 26 "go.chromium.org/luci/common/retry/transient" 27 "go.chromium.org/luci/gae/filter/txndefer" 28 "go.chromium.org/luci/gae/impl/memory" 29 "go.chromium.org/luci/gae/service/datastore" 30 "go.chromium.org/luci/server/auth" 31 "go.chromium.org/luci/server/auth/authtest" 32 "go.chromium.org/luci/server/tq" 33 "go.chromium.org/luci/server/tq/tqtesting" 34 35 "go.chromium.org/luci/buildbucket/appengine/internal/clients" 36 "go.chromium.org/luci/buildbucket/appengine/internal/compression" 37 "go.chromium.org/luci/buildbucket/appengine/model" 38 taskdefs "go.chromium.org/luci/buildbucket/appengine/tasks/defs" 39 pb "go.chromium.org/luci/buildbucket/proto" 40 41 . "github.com/smartystreets/goconvey/convey" 42 43 . "go.chromium.org/luci/common/testing/assertions" 44 ) 45 46 func TestNotification(t *testing.T) { 47 t.Parallel() 48 49 Convey("notifyPubsub", t, func() { 50 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{}) 51 ctx = txndefer.FilterRDS(ctx) 52 ctx, sch := tq.TestingContext(ctx, nil) 53 datastore.GetTestable(ctx).AutoIndex(true) 54 datastore.GetTestable(ctx).Consistent(true) 55 56 sortTasksByClassName := func(tasks tqtesting.TaskList) { 57 sort.Slice(tasks, func(i, j int) bool { 58 return tasks[i].Class < tasks[j].Class 59 }) 60 } 61 62 Convey("w/o callback", func() { 63 txErr := datastore.RunInTransaction(ctx, func(ctx context.Context) error { 64 return NotifyPubSub(ctx, &model.Build{ 65 ID: 123, 66 Proto: &pb.Build{ 67 Builder: &pb.BuilderID{ 68 Project: "project", 69 }, 70 }, 71 }) 72 }, nil) 73 So(txErr, ShouldBeNil) 74 tasks := sch.Tasks() 75 sortTasksByClassName(tasks) 76 So(tasks, ShouldHaveLength, 2) 77 So(tasks[0].Payload.(*taskdefs.NotifyPubSub).GetBuildId(), ShouldEqual, 123) 78 So(tasks[0].Payload.(*taskdefs.NotifyPubSub).GetCallback(), ShouldBeFalse) 79 So(tasks[1].Payload.(*taskdefs.NotifyPubSubGoProxy).GetBuildId(), ShouldEqual, 123) 80 So(tasks[1].Payload.(*taskdefs.NotifyPubSubGoProxy).GetProject(), ShouldEqual, "project") 81 }) 82 83 Convey("w/ callback", func() { 84 txErr := datastore.RunInTransaction(ctx, func(ctx context.Context) error { 85 cb := model.PubSubCallback{ 86 AuthToken: "token", 87 Topic: "topic", 88 UserData: []byte("user_data"), 89 } 90 return NotifyPubSub(ctx, &model.Build{ 91 ID: 123, 92 PubSubCallback: cb, 93 Proto: &pb.Build{ 94 Builder: &pb.BuilderID{ 95 Project: "project", 96 }, 97 }, 98 }) 99 }, nil) 100 So(txErr, ShouldBeNil) 101 tasks := sch.Tasks() 102 sortTasksByClassName(tasks) 103 So(tasks, ShouldHaveLength, 3) 104 105 n1 := tasks[0].Payload.(*taskdefs.NotifyPubSub) 106 n2 := tasks[1].Payload.(*taskdefs.NotifyPubSubGo) 107 So(n1.GetBuildId(), ShouldEqual, 123) 108 So(n1.GetCallback(), ShouldBeFalse) 109 So(n2.GetBuildId(), ShouldEqual, 123) 110 So(n2.GetCallback(), ShouldBeTrue) 111 So(n2.GetTopic().GetName(), ShouldEqual, "topic") 112 113 So(tasks[2].Payload.(*taskdefs.NotifyPubSubGoProxy).GetBuildId(), ShouldEqual, 123) 114 So(tasks[2].Payload.(*taskdefs.NotifyPubSubGoProxy).GetProject(), ShouldEqual, "project") 115 }) 116 117 Convey("w/ callback (Python)", func() { 118 txErr := datastore.RunInTransaction(ctx, func(ctx context.Context) error { 119 cb := model.PubSubCallback{ 120 AuthToken: "token", 121 Topic: "projects/flutter-dashboard/topics/luci-builds", 122 UserData: []byte("user_data"), 123 } 124 return NotifyPubSub(ctx, &model.Build{ 125 ID: 123, 126 PubSubCallback: cb, 127 Proto: &pb.Build{ 128 Builder: &pb.BuilderID{ 129 Project: "project", 130 }, 131 }, 132 }) 133 }, nil) 134 So(txErr, ShouldBeNil) 135 tasks := sch.Tasks() 136 sortTasksByClassName(tasks) 137 So(tasks, ShouldHaveLength, 3) 138 139 n1 := tasks[0].Payload.(*taskdefs.NotifyPubSub) 140 n2 := tasks[1].Payload.(*taskdefs.NotifyPubSub) 141 So(n1.GetBuildId(), ShouldEqual, 123) 142 So(n2.GetBuildId(), ShouldEqual, 123) 143 // One w/ callback and one w/o callback. 144 So(n1.GetCallback() != n2.GetCallback(), ShouldBeTrue) 145 146 So(tasks[2].Payload.(*taskdefs.NotifyPubSubGoProxy).GetBuildId(), ShouldEqual, 123) 147 So(tasks[2].Payload.(*taskdefs.NotifyPubSubGoProxy).GetProject(), ShouldEqual, "project") 148 }) 149 }) 150 151 Convey("EnqueueNotifyPubSubGo", t, func() { 152 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{}) 153 ctx = txndefer.FilterRDS(ctx) 154 ctx, sch := tq.TestingContext(ctx, nil) 155 datastore.GetTestable(ctx).AutoIndex(true) 156 datastore.GetTestable(ctx).Consistent(true) 157 So(datastore.Put(ctx, &model.Project{ 158 ID: "project_with_external_topics", 159 CommonConfig: &pb.BuildbucketCfg_CommonConfig{ 160 BuildsNotificationTopics: []*pb.BuildbucketCfg_Topic{ 161 { 162 Name: "projects/my-cloud-project/topics/my-topic", 163 }, 164 }, 165 }, 166 }), ShouldBeNil) 167 168 Convey("no project entity", func() { 169 txErr := EnqueueNotifyPubSubGo(ctx, 123, "project_no_external_topics") 170 So(txErr, ShouldBeNil) 171 tasks := sch.Tasks() 172 So(tasks, ShouldHaveLength, 1) 173 So(tasks[0].Payload, ShouldResembleProto, &taskdefs.NotifyPubSubGo{ 174 BuildId: 123, 175 }) 176 }) 177 178 Convey("empty project.common_config", func() { 179 So(datastore.Put(ctx, &model.Project{ 180 ID: "project_empty", 181 CommonConfig: &pb.BuildbucketCfg_CommonConfig{}, 182 }), ShouldBeNil) 183 txErr := EnqueueNotifyPubSubGo(ctx, 123, "project_empty") 184 So(txErr, ShouldBeNil) 185 tasks := sch.Tasks() 186 So(tasks, ShouldHaveLength, 1) 187 So(tasks[0].Payload, ShouldResembleProto, &taskdefs.NotifyPubSubGo{ 188 BuildId: 123, 189 }) 190 }) 191 192 Convey("has external topics", func() { 193 txErr := EnqueueNotifyPubSubGo(ctx, 123, "project_with_external_topics") 194 So(txErr, ShouldBeNil) 195 tasks := sch.Tasks() 196 So(tasks, ShouldHaveLength, 2) 197 taskGo0 := tasks[0].Payload.(*taskdefs.NotifyPubSubGo) 198 taskGo1 := tasks[1].Payload.(*taskdefs.NotifyPubSubGo) 199 So(taskGo0.BuildId, ShouldEqual, 123) 200 So(taskGo1.BuildId, ShouldEqual, 123) 201 So(taskGo0.Topic.GetName()+taskGo1.Topic.GetName(), ShouldEqual, "projects/my-cloud-project/topics/my-topic") 202 }) 203 }) 204 205 Convey("PublishBuildsV2Notification", t, func() { 206 ctx := auth.WithState(memory.Use(context.Background()), &authtest.FakeState{}) 207 ctx = txndefer.FilterRDS(ctx) 208 ctx, sch := tq.TestingContext(ctx, nil) 209 datastore.GetTestable(ctx).AutoIndex(true) 210 datastore.GetTestable(ctx).Consistent(true) 211 212 b := &model.Build{ 213 ID: 123, 214 Proto: &pb.Build{ 215 Id: 123, 216 Builder: &pb.BuilderID{ 217 Project: "project", 218 Bucket: "bucket", 219 Builder: "builder", 220 }, 221 Status: pb.Status_CANCELED, 222 }, 223 } 224 bk := datastore.KeyForObj(ctx, b) 225 bsBytes, err := proto.Marshal(&pb.Build{ 226 Steps: []*pb.Step{ 227 { 228 Name: "step", 229 SummaryMarkdown: "summary", 230 Logs: []*pb.Log{{ 231 Name: "log1", 232 Url: "url", 233 ViewUrl: "view_url", 234 }, 235 }, 236 }, 237 }, 238 }) 239 So(err, ShouldBeNil) 240 bs := &model.BuildSteps{ID: 1, Build: bk, Bytes: bsBytes} 241 bi := &model.BuildInfra{ 242 ID: 1, 243 Build: bk, 244 Proto: &pb.BuildInfra{ 245 Buildbucket: &pb.BuildInfra_Buildbucket{ 246 Hostname: "hostname", 247 }, 248 }, 249 } 250 bo := &model.BuildOutputProperties{ 251 Build: bk, 252 Proto: &structpb.Struct{ 253 Fields: map[string]*structpb.Value{ 254 "output": { 255 Kind: &structpb.Value_StringValue{ 256 StringValue: "output value", 257 }, 258 }, 259 }, 260 }, 261 } 262 binpProp := &model.BuildInputProperties{ 263 Build: bk, 264 Proto: &structpb.Struct{ 265 Fields: map[string]*structpb.Value{ 266 "input": { 267 Kind: &structpb.Value_StringValue{ 268 StringValue: "input value", 269 }, 270 }, 271 }, 272 }, 273 } 274 So(datastore.Put(ctx, b, bi, bs, bo, binpProp), ShouldBeNil) 275 276 Convey("build not exist", func() { 277 err := PublishBuildsV2Notification(ctx, 999, nil, false) 278 So(err, ShouldBeNil) 279 tasks := sch.Tasks() 280 So(tasks, ShouldHaveLength, 0) 281 }) 282 283 Convey("To internal topic", func() { 284 285 Convey("success", func() { 286 err := PublishBuildsV2Notification(ctx, 123, nil, false) 287 So(err, ShouldBeNil) 288 289 tasks := sch.Tasks() 290 So(tasks, ShouldHaveLength, 1) 291 So(tasks[0].Message.Attributes["project"], ShouldEqual, "project") 292 So(tasks[0].Message.Attributes["is_completed"], ShouldEqual, "true") 293 So(tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuild(), ShouldResembleProto, &pb.Build{ 294 Id: 123, 295 Builder: &pb.BuilderID{ 296 Project: "project", 297 Bucket: "bucket", 298 Builder: "builder", 299 }, 300 Status: pb.Status_CANCELED, 301 Infra: &pb.BuildInfra{ 302 Buildbucket: &pb.BuildInfra_Buildbucket{ 303 Hostname: "hostname", 304 }, 305 }, 306 Input: &pb.Build_Input{}, 307 Output: &pb.Build_Output{}, 308 }) 309 So(tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuildLargeFields(), ShouldNotBeNil) 310 bLargeBytes := tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuildLargeFields() 311 buildLarge, err := zlibUncompressBuild(bLargeBytes) 312 So(err, ShouldBeNil) 313 So(buildLarge, ShouldResembleProto, &pb.Build{ 314 Steps: []*pb.Step{ 315 { 316 Name: "step", 317 SummaryMarkdown: "summary", 318 Logs: []*pb.Log{{ 319 Name: "log1", 320 Url: "url", 321 ViewUrl: "view_url", 322 }, 323 }, 324 }, 325 }, 326 Input: &pb.Build_Input{ 327 Properties: &structpb.Struct{ 328 Fields: map[string]*structpb.Value{ 329 "input": { 330 Kind: &structpb.Value_StringValue{ 331 StringValue: "input value", 332 }, 333 }, 334 }, 335 }, 336 }, 337 Output: &pb.Build_Output{ 338 Properties: &structpb.Struct{ 339 Fields: map[string]*structpb.Value{ 340 "output": { 341 Kind: &structpb.Value_StringValue{ 342 StringValue: "output value", 343 }, 344 }, 345 }, 346 }, 347 }, 348 }) 349 }) 350 351 Convey("success - no large fields", func() { 352 b := &model.Build{ 353 ID: 456, 354 Proto: &pb.Build{ 355 Id: 456, 356 Builder: &pb.BuilderID{ 357 Project: "project", 358 Bucket: "bucket", 359 Builder: "builder", 360 }, 361 Status: pb.Status_CANCELED, 362 }, 363 } 364 bk := datastore.KeyForObj(ctx, b) 365 bi := &model.BuildInfra{ 366 ID: 1, 367 Build: bk, 368 Proto: &pb.BuildInfra{ 369 Buildbucket: &pb.BuildInfra_Buildbucket{ 370 Hostname: "hostname", 371 }, 372 }, 373 } 374 So(datastore.Put(ctx, b, bi), ShouldBeNil) 375 376 err := PublishBuildsV2Notification(ctx, 456, nil, false) 377 So(err, ShouldBeNil) 378 379 tasks := sch.Tasks() 380 So(tasks, ShouldHaveLength, 1) 381 So(tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuild(), ShouldResembleProto, &pb.Build{ 382 Id: 456, 383 Builder: &pb.BuilderID{ 384 Project: "project", 385 Bucket: "bucket", 386 Builder: "builder", 387 }, 388 Status: pb.Status_CANCELED, 389 Infra: &pb.BuildInfra{ 390 Buildbucket: &pb.BuildInfra_Buildbucket{ 391 Hostname: "hostname", 392 }, 393 }, 394 Input: &pb.Build_Input{}, 395 Output: &pb.Build_Output{}, 396 }) 397 So(tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuildLargeFields(), ShouldNotBeNil) 398 bLargeBytes := tasks[0].Payload.(*pb.BuildsV2PubSub).GetBuildLargeFields() 399 buildLarge, err := zlibUncompressBuild(bLargeBytes) 400 So(err, ShouldBeNil) 401 So(buildLarge, ShouldResembleProto, &pb.Build{ 402 Input: &pb.Build_Input{}, 403 Output: &pb.Build_Output{}, 404 }) 405 }) 406 }) 407 408 Convey("To external topic (non callback)", func() { 409 ctx, psserver, psclient, err := clients.SetupTestPubsub(ctx, "my-cloud-project") 410 So(err, ShouldBeNil) 411 defer func() { 412 psclient.Close() 413 psserver.Close() 414 }() 415 _, err = psclient.CreateTopic(ctx, "my-topic") 416 So(err, ShouldBeNil) 417 418 Convey("success (zlib compression)", func() { 419 err := PublishBuildsV2Notification(ctx, 123, &pb.BuildbucketCfg_Topic{Name: "projects/my-cloud-project/topics/my-topic"}, false) 420 So(err, ShouldBeNil) 421 422 tasks := sch.Tasks() 423 So(tasks, ShouldHaveLength, 0) 424 So(psserver.Messages(), ShouldHaveLength, 1) 425 publishedMsg := psserver.Messages()[0] 426 427 So(publishedMsg.Attributes["project"], ShouldEqual, "project") 428 So(publishedMsg.Attributes["bucket"], ShouldEqual, "bucket") 429 So(publishedMsg.Attributes["builder"], ShouldEqual, "builder") 430 So(publishedMsg.Attributes["is_completed"], ShouldEqual, "true") 431 buildMsg := &pb.BuildsV2PubSub{} 432 err = protojson.Unmarshal(publishedMsg.Data, buildMsg) 433 So(err, ShouldBeNil) 434 So(buildMsg.Build, ShouldResembleProto, &pb.Build{ 435 Id: 123, 436 Builder: &pb.BuilderID{ 437 Project: "project", 438 Bucket: "bucket", 439 Builder: "builder", 440 }, 441 Status: pb.Status_CANCELED, 442 Infra: &pb.BuildInfra{ 443 Buildbucket: &pb.BuildInfra_Buildbucket{ 444 Hostname: "hostname", 445 }, 446 }, 447 Input: &pb.Build_Input{}, 448 Output: &pb.Build_Output{}, 449 }) 450 So(buildMsg.BuildLargeFields, ShouldNotBeNil) 451 buildLarge, err := zlibUncompressBuild(buildMsg.BuildLargeFields) 452 So(err, ShouldBeNil) 453 So(buildLarge, ShouldResembleProto, &pb.Build{ 454 Steps: []*pb.Step{ 455 { 456 Name: "step", 457 SummaryMarkdown: "summary", 458 Logs: []*pb.Log{{ 459 Name: "log1", 460 Url: "url", 461 ViewUrl: "view_url", 462 }, 463 }, 464 }, 465 }, 466 Input: &pb.Build_Input{ 467 Properties: &structpb.Struct{ 468 Fields: map[string]*structpb.Value{ 469 "input": { 470 Kind: &structpb.Value_StringValue{ 471 StringValue: "input value", 472 }, 473 }, 474 }, 475 }, 476 }, 477 Output: &pb.Build_Output{ 478 Properties: &structpb.Struct{ 479 Fields: map[string]*structpb.Value{ 480 "output": { 481 Kind: &structpb.Value_StringValue{ 482 StringValue: "output value", 483 }, 484 }, 485 }, 486 }, 487 }, 488 }) 489 }) 490 491 Convey("success (zstd compression)", func() { 492 err := PublishBuildsV2Notification(ctx, 123, &pb.BuildbucketCfg_Topic{ 493 Name: "projects/my-cloud-project/topics/my-topic", 494 Compression: pb.Compression_ZSTD, 495 }, false) 496 So(err, ShouldBeNil) 497 498 tasks := sch.Tasks() 499 So(tasks, ShouldHaveLength, 0) 500 So(psserver.Messages(), ShouldHaveLength, 1) 501 publishedMsg := psserver.Messages()[0] 502 503 So(publishedMsg.Attributes["project"], ShouldEqual, "project") 504 So(publishedMsg.Attributes["bucket"], ShouldEqual, "bucket") 505 So(publishedMsg.Attributes["builder"], ShouldEqual, "builder") 506 So(publishedMsg.Attributes["is_completed"], ShouldEqual, "true") 507 buildMsg := &pb.BuildsV2PubSub{} 508 err = protojson.Unmarshal(publishedMsg.Data, buildMsg) 509 So(err, ShouldBeNil) 510 So(buildMsg.Build, ShouldResembleProto, &pb.Build{ 511 Id: 123, 512 Builder: &pb.BuilderID{ 513 Project: "project", 514 Bucket: "bucket", 515 Builder: "builder", 516 }, 517 Status: pb.Status_CANCELED, 518 Infra: &pb.BuildInfra{ 519 Buildbucket: &pb.BuildInfra_Buildbucket{ 520 Hostname: "hostname", 521 }, 522 }, 523 Input: &pb.Build_Input{}, 524 Output: &pb.Build_Output{}, 525 }) 526 So(buildMsg.BuildLargeFields, ShouldNotBeNil) 527 So(buildMsg.Compression, ShouldEqual, pb.Compression_ZSTD) 528 buildLarge, err := zstdUncompressBuild(buildMsg.BuildLargeFields) 529 So(err, ShouldBeNil) 530 So(buildLarge, ShouldResembleProto, &pb.Build{ 531 Steps: []*pb.Step{ 532 { 533 Name: "step", 534 SummaryMarkdown: "summary", 535 Logs: []*pb.Log{{ 536 Name: "log1", 537 Url: "url", 538 ViewUrl: "view_url", 539 }, 540 }, 541 }, 542 }, 543 Input: &pb.Build_Input{ 544 Properties: &structpb.Struct{ 545 Fields: map[string]*structpb.Value{ 546 "input": { 547 Kind: &structpb.Value_StringValue{ 548 StringValue: "input value", 549 }, 550 }, 551 }, 552 }, 553 }, 554 Output: &pb.Build_Output{ 555 Properties: &structpb.Struct{ 556 Fields: map[string]*structpb.Value{ 557 "output": { 558 Kind: &structpb.Value_StringValue{ 559 StringValue: "output value", 560 }, 561 }, 562 }, 563 }, 564 }, 565 }) 566 }) 567 568 Convey("non-exist topic", func() { 569 err := PublishBuildsV2Notification(ctx, 123, &pb.BuildbucketCfg_Topic{ 570 Name: "projects/my-cloud-project/topics/non-exist-topic", 571 }, false) 572 So(err, ShouldNotBeNil) 573 So(transient.Tag.In(err), ShouldBeTrue) 574 }) 575 }) 576 577 Convey("To external topic (callback)", func() { 578 ctx, psserver, psclient, err := clients.SetupTestPubsub(ctx, "my-cloud-project") 579 So(err, ShouldBeNil) 580 defer func() { 581 psclient.Close() 582 psserver.Close() 583 }() 584 _, err = psclient.CreateTopic(ctx, "callback-topic") 585 So(err, ShouldBeNil) 586 587 So(datastore.Put(ctx, &model.Build{ 588 ID: 999, 589 Project: "project", 590 BucketID: "bucket", 591 BuilderID: "builder", 592 Proto: &pb.Build{ 593 Id: 999, 594 Builder: &pb.BuilderID{ 595 Project: "project", 596 Bucket: "bucket", 597 Builder: "builder", 598 }, 599 }, 600 PubSubCallback: model.PubSubCallback{ 601 Topic: "projects/my-cloud-project/topics/callback-topic", 602 UserData: []byte("userdata"), 603 }, 604 }), ShouldBeNil) 605 606 err = PublishBuildsV2Notification(ctx, 999, &pb.BuildbucketCfg_Topic{Name: "projects/my-cloud-project/topics/callback-topic"}, true) 607 So(err, ShouldBeNil) 608 609 tasks := sch.Tasks() 610 So(tasks, ShouldHaveLength, 0) 611 So(psserver.Messages(), ShouldHaveLength, 1) 612 publishedMsg := psserver.Messages()[0] 613 614 So(publishedMsg.Attributes["project"], ShouldEqual, "project") 615 So(publishedMsg.Attributes["bucket"], ShouldEqual, "bucket") 616 So(publishedMsg.Attributes["builder"], ShouldEqual, "builder") 617 So(publishedMsg.Attributes["is_completed"], ShouldEqual, "false") 618 psCallbackMsg := &pb.PubSubCallBack{} 619 err = protojson.Unmarshal(publishedMsg.Data, psCallbackMsg) 620 So(err, ShouldBeNil) 621 622 buildLarge, err := zlibUncompressBuild(psCallbackMsg.BuildPubsub.BuildLargeFields) 623 So(err, ShouldBeNil) 624 So(buildLarge, ShouldResembleProto, &pb.Build{Input: &pb.Build_Input{}, Output: &pb.Build_Output{}}) 625 So(psCallbackMsg.BuildPubsub.Build, ShouldResembleProto, &pb.Build{ 626 Id: 999, 627 Builder: &pb.BuilderID{ 628 Project: "project", 629 Bucket: "bucket", 630 Builder: "builder", 631 }, 632 Input: &pb.Build_Input{}, 633 Output: &pb.Build_Output{}, 634 }) 635 So(err, ShouldBeNil) 636 So(psCallbackMsg.UserData, ShouldResemble, []byte("userdata")) 637 }) 638 }) 639 } 640 641 func zlibUncompressBuild(compressed []byte) (*pb.Build, error) { 642 originalData, err := compression.ZlibDecompress(compressed) 643 if err != nil { 644 return nil, err 645 } 646 b := &pb.Build{} 647 if err := proto.Unmarshal(originalData, b); err != nil { 648 return nil, err 649 } 650 return b, nil 651 } 652 653 func zstdUncompressBuild(compressed []byte) (*pb.Build, error) { 654 originalData, err := compression.ZstdDecompress(compressed, nil) 655 if err != nil { 656 return nil, err 657 } 658 b := &pb.Build{} 659 if err := proto.Unmarshal(originalData, b); err != nil { 660 return nil, err 661 } 662 return b, nil 663 }