github.com/thiagoyeds/go-cloud@v0.26.0/pubsub/drivertest/drivertest.go (about) 1 // Copyright 2018 The Go Cloud Development Kit 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 // https://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 drivertest provides a conformance test for implementations of 16 // driver. 17 package drivertest // import "gocloud.dev/pubsub/drivertest" 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "sort" 24 "strconv" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 "gocloud.dev/gcerrors" 31 "gocloud.dev/internal/escape" 32 "gocloud.dev/internal/retry" 33 "gocloud.dev/pubsub" 34 "gocloud.dev/pubsub/batcher" 35 "gocloud.dev/pubsub/driver" 36 "golang.org/x/sync/errgroup" 37 ) 38 39 // Harness descibes the functionality test harnesses must provide to run 40 // conformance tests. 41 type Harness interface { 42 // CreateTopic creates a new topic and returns a driver.Topic 43 // for testing. The topic may have to be removed manually if the test is 44 // abruptly terminated or the network connection fails. 45 CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) 46 47 // MakeNonexistentTopic makes a driver.Topic referencing a topic that 48 // does not exist. 49 MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) 50 51 // CreateSubscription creates a new subscription, subscribed 52 // to the given topic, and returns a driver.Subscription for testing. The 53 // subscription may have to be cleaned up manually if the test is abruptly 54 // terminated or the network connection fails. 55 CreateSubscription(ctx context.Context, t driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) 56 57 // MakeNonexistentSubscription makes a driver.Subscription referencing a 58 // subscription that does not exist. 59 MakeNonexistentSubscription(ctx context.Context) (ds driver.Subscription, cleanup func(), err error) 60 61 // Close closes resources used by the harness, but does not call Close 62 // on the Topics and Subscriptions generated by the Harness. 63 Close() 64 65 // MaxBatchSizes returns the maximum size of SendBatch/Send(Na|A)cks, or 0 66 // if there's no max. 67 MaxBatchSizes() (int, int) 68 69 // SupportsMultipleSubscriptions reports whether the driver supports 70 // multiple subscriptions for the same topic. 71 SupportsMultipleSubscriptions() bool 72 } 73 74 // HarnessMaker describes functions that construct a harness for running tests. 75 // It is called exactly once per test; Harness.Close() will be called when the test is complete. 76 type HarnessMaker func(ctx context.Context, t *testing.T) (Harness, error) 77 78 // AsTest represents a test of As functionality. 79 // The conformance test: 80 // 1. Calls TopicCheck. 81 // 2. Calls SubscriptionCheck. 82 // 3. Sends a message, setting Message.BeforeSend to BeforeSend 83 // and Message.AfterSend to AfterSend. 84 // 4. Receives the message and calls MessageCheck. 85 // 5. Calls TopicErrorCheck. 86 // 6. Calls SubscriptionErrorCheck. 87 type AsTest interface { 88 // Name should return a descriptive name for the test. 89 Name() string 90 // TopicCheck will be called to allow verifcation of Topic.As. 91 TopicCheck(t *pubsub.Topic) error 92 // SubscriptionCheck will be called to allow verification of Subscription.As. 93 SubscriptionCheck(s *pubsub.Subscription) error 94 // TopicErrorCheck will be called to allow verification of Topic.ErrorAs. 95 // The error will be the one returned from SendBatch when called with 96 // a non-existent topic. 97 TopicErrorCheck(t *pubsub.Topic, err error) error 98 // SubscriptionErrorCheck will be called to allow verification of 99 // Subscription.ErrorAs. 100 // The error will be the one returned from ReceiveBatch when called with 101 // a non-existent subscription. 102 SubscriptionErrorCheck(s *pubsub.Subscription, err error) error 103 // MessageCheck will be called to allow verification of Message.As. 104 MessageCheck(m *pubsub.Message) error 105 // BeforeSend will be used as Message.BeforeSend as part of sending a test 106 // message. 107 BeforeSend(as func(interface{}) bool) error 108 // AfterSend will be used as Message.AfterSend as part of sending a test 109 // message. 110 AfterSend(as func(interface{}) bool) error 111 } 112 113 // Many tests set the maximum batch size to 1 to make record/replay stable. 114 var batchSizeOne = &batcher.Options{MaxBatchSize: 1, MaxHandlers: 1} 115 116 type verifyAsFailsOnNil struct{} 117 118 func (verifyAsFailsOnNil) Name() string { 119 return "verify As returns false when passed nil" 120 } 121 122 func (verifyAsFailsOnNil) TopicCheck(t *pubsub.Topic) error { 123 if t.As(nil) { 124 return errors.New("want Topic.As to return false when passed nil") 125 } 126 return nil 127 } 128 129 func (verifyAsFailsOnNil) SubscriptionCheck(s *pubsub.Subscription) error { 130 if s.As(nil) { 131 return errors.New("want Subscription.As to return false when passed nil") 132 } 133 return nil 134 } 135 136 func (verifyAsFailsOnNil) TopicErrorCheck(t *pubsub.Topic, err error) (ret error) { 137 defer func() { 138 if recover() == nil { 139 ret = errors.New("want Topic.ErrorAs to panic when passed nil") 140 } 141 }() 142 t.ErrorAs(err, nil) 143 return nil 144 } 145 146 func (verifyAsFailsOnNil) SubscriptionErrorCheck(s *pubsub.Subscription, err error) (ret error) { 147 defer func() { 148 if recover() == nil { 149 ret = errors.New("want Subscription.ErrorAs to panic when passed nil") 150 } 151 }() 152 s.ErrorAs(err, nil) 153 return nil 154 } 155 156 func (verifyAsFailsOnNil) MessageCheck(m *pubsub.Message) error { 157 if m.As(nil) { 158 return errors.New("want Message.As to return false when passed nil") 159 } 160 return nil 161 } 162 163 func (verifyAsFailsOnNil) BeforeSend(as func(interface{}) bool) error { 164 if as(nil) { 165 return errors.New("want Message.BeforeSend's As function to return false when passed nil") 166 } 167 return nil 168 } 169 170 func (verifyAsFailsOnNil) AfterSend(as func(interface{}) bool) error { 171 if as(nil) { 172 return errors.New("want Message.AfterSend's As function to return false when passed nil") 173 } 174 return nil 175 } 176 177 // RunConformanceTests runs conformance tests for driver implementations of pubsub. 178 func RunConformanceTests(t *testing.T, newHarness HarnessMaker, asTests []AsTest) { 179 tests := map[string]func(t *testing.T, newHarness HarnessMaker){ 180 "TestSendReceive": testSendReceive, 181 "TestSendReceiveTwo": testSendReceiveTwo, 182 "TestSendReceiveJSON": testSendReceiveJSON, 183 "TestNack": testNack, 184 "TestBatching": testBatching, 185 "TestDoubleAck": testDoubleAck, 186 "TestErrorOnSendToClosedTopic": testErrorOnSendToClosedTopic, 187 "TestErrorOnReceiveFromClosedSubscription": testErrorOnReceiveFromClosedSubscription, 188 "TestCancelSendReceive": testCancelSendReceive, 189 "TestNonExistentTopicSucceedsOnOpenButFailsOnSend": testNonExistentTopicSucceedsOnOpenButFailsOnSend, 190 "TestNonExistentSubscriptionSucceedsOnOpenButFailsOnReceive": testNonExistentSubscriptionSucceedsOnOpenButFailsOnReceive, 191 "TestMetadata": testMetadata, 192 "TestNonUTF8MessageBody": testNonUTF8MessageBody, 193 } 194 for name, test := range tests { 195 t.Run(name, func(t *testing.T) { test(t, newHarness) }) 196 } 197 198 asTests = append(asTests, verifyAsFailsOnNil{}) 199 t.Run("TestAs", func(t *testing.T) { 200 for _, st := range asTests { 201 if st.Name() == "" { 202 t.Fatalf("AsTest.Name is required") 203 } 204 t.Run(st.Name(), func(t *testing.T) { testAs(t, newHarness, st) }) 205 } 206 }) 207 } 208 209 // RunBenchmarks runs benchmarks for driver implementations of pubsub. 210 func RunBenchmarks(b *testing.B, topic *pubsub.Topic, sub *pubsub.Subscription) { 211 b.Run("BenchmarkReceive", func(b *testing.B) { 212 benchmark(b, topic, sub, false) 213 }) 214 b.Run("BenchmarkSend", func(b *testing.B) { 215 benchmark(b, topic, sub, true) 216 }) 217 } 218 219 func testNonExistentTopicSucceedsOnOpenButFailsOnSend(t *testing.T, newHarness HarnessMaker) { 220 // Set up. 221 ctx := context.Background() 222 h, err := newHarness(ctx, t) 223 if err != nil { 224 t.Fatal(err) 225 } 226 defer h.Close() 227 228 dt, err := h.MakeNonexistentTopic(ctx) 229 if err != nil { 230 // Failure shouldn't happen for non-existent topics until messages are sent 231 // to them. 232 t.Fatalf("creating a local topic that doesn't exist on the server: %v", err) 233 } 234 topic := pubsub.NewTopic(dt, nil) 235 defer func() { 236 if err := topic.Shutdown(ctx); err != nil { 237 t.Error(err) 238 } 239 }() 240 241 m := &pubsub.Message{} 242 err = topic.Send(ctx, m) 243 if err == nil || gcerrors.Code(err) != gcerrors.NotFound { 244 t.Errorf("got error %v for send to non-existent topic, want code=NotFound", err) 245 } 246 } 247 248 func testNonExistentSubscriptionSucceedsOnOpenButFailsOnReceive(t *testing.T, newHarness HarnessMaker) { 249 // Set up. 250 ctx := context.Background() 251 h, err := newHarness(ctx, t) 252 if err != nil { 253 t.Fatal(err) 254 } 255 defer h.Close() 256 257 ds, cleanup, err := h.MakeNonexistentSubscription(ctx) 258 if err != nil { 259 t.Fatalf("failed to make non-existent subscription: %v", err) 260 } 261 defer cleanup() 262 sub := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 263 defer func() { 264 if err := sub.Shutdown(ctx); err != nil { 265 t.Error(err) 266 } 267 }() 268 269 // The test will hang here if the message isn't available, so use a shorter timeout. 270 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 271 defer cancel() 272 _, err = sub.Receive(ctx2) 273 if err == nil || ctx2.Err() != nil || gcerrors.Code(err) != gcerrors.NotFound { 274 t.Errorf("got error %v for receive from non-existent subscription, want code=NotFound", err) 275 } 276 } 277 278 func testSendReceive(t *testing.T, newHarness HarnessMaker) { 279 // Set up. 280 ctx := context.Background() 281 h, err := newHarness(ctx, t) 282 if err != nil { 283 t.Fatal(err) 284 } 285 defer h.Close() 286 topic, sub, cleanup, err := makePair(ctx, t, h) 287 if err != nil { 288 t.Fatal(err) 289 } 290 defer cleanup() 291 292 want := publishN(ctx, t, topic, 3) 293 got := receiveN(ctx, t, sub, len(want)) 294 295 // Verify LoggableID is set. 296 for _, msg := range got { 297 if msg.LoggableID == "" { 298 t.Errorf("msg.LoggableID was empty, should be set") 299 } 300 } 301 302 // Check that the received messages match the sent ones. 303 if diff := diffMessageSets(got, want); diff != "" { 304 t.Error(diff) 305 } 306 } 307 308 // Receive from two subscriptions to the same topic. 309 // Verify both get all the messages. 310 func testSendReceiveTwo(t *testing.T, newHarness HarnessMaker) { 311 // Set up. 312 ctx := context.Background() 313 h, err := newHarness(ctx, t) 314 if err != nil { 315 t.Fatal(err) 316 } 317 defer h.Close() 318 if !h.SupportsMultipleSubscriptions() { 319 t.Skip("multiple subscriptions to a topic not supported") 320 } 321 322 dt, cleanup, err := h.CreateTopic(ctx, t.Name()) 323 if err != nil { 324 t.Fatal(err) 325 } 326 defer cleanup() 327 topic := pubsub.NewTopic(dt, batchSizeOne) 328 defer func() { 329 if err := topic.Shutdown(ctx); err != nil { 330 t.Error(err) 331 } 332 }() 333 334 var ss []*pubsub.Subscription 335 for i := 0; i < 2; i++ { 336 ds, cleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 337 if err != nil { 338 t.Fatal(err) 339 } 340 defer cleanup() 341 s := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 342 defer func() { 343 if err := s.Shutdown(ctx); err != nil { 344 t.Error(err) 345 } 346 }() 347 ss = append(ss, s) 348 } 349 want := publishN(ctx, t, topic, 3) 350 for i, s := range ss { 351 got := receiveN(ctx, t, s, len(want)) 352 if diff := diffMessageSets(got, want); diff != "" { 353 t.Errorf("sub #%d: %s", i, diff) 354 } 355 } 356 } 357 358 func testSendReceiveJSON(t *testing.T, newHarness HarnessMaker) { 359 const json = `{"Foo": "Bar"}` 360 // Set up. 361 ctx := context.Background() 362 h, err := newHarness(ctx, t) 363 if err != nil { 364 t.Fatal(err) 365 } 366 defer h.Close() 367 topic, sub, cleanup, err := makePair(ctx, t, h) 368 if err != nil { 369 t.Fatal(err) 370 } 371 defer cleanup() 372 373 sendM := &pubsub.Message{Body: []byte(json)} 374 if err := topic.Send(ctx, sendM); err != nil { 375 t.Fatal(err) 376 } 377 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 378 defer cancel() 379 receiveM, err := sub.Receive(ctx2) 380 if err != nil { 381 t.Fatal(err) 382 } 383 receiveM.Ack() 384 if diff := diffMessageSets([]*pubsub.Message{receiveM}, []*pubsub.Message{sendM}); diff != "" { 385 t.Error(diff) 386 } 387 } 388 389 func testNack(t *testing.T, newHarness HarnessMaker) { 390 const nMessages = 2 391 392 // Set up. 393 ctx := context.Background() 394 h, err := newHarness(ctx, t) 395 if err != nil { 396 t.Fatal(err) 397 } 398 defer h.Close() 399 dt, topicCleanup, err := h.CreateTopic(ctx, t.Name()) 400 if err != nil { 401 t.Fatal(err) 402 } 403 defer topicCleanup() 404 ds, subCleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 405 if err != nil { 406 t.Fatal(err) 407 } 408 defer subCleanup() 409 if !ds.CanNack() { 410 t.Skip("Nack not supported") 411 } 412 topic := pubsub.NewTopic(dt, batchSizeOne) 413 defer func() { 414 if err := topic.Shutdown(ctx); err != nil { 415 t.Error(err) 416 } 417 }() 418 sub := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 419 defer func() { 420 if err := sub.Shutdown(ctx); err != nil { 421 t.Error(err) 422 } 423 }() 424 425 want := publishN(ctx, t, topic, nMessages) 426 427 // Get the messages, but nack them. 428 // Make sure to nack after receiving all of them; otherwise, we could 429 // receive one of the messages twice instead of receiving all nMessages. 430 // The test will hang here if the messages aren't redelivered, so use a shorter timeout. 431 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 432 defer cancel() 433 var got []*pubsub.Message 434 for i := 0; i < nMessages; i++ { 435 m, err := sub.Receive(ctx2) 436 if err != nil { 437 t.Fatal(err) 438 } 439 got = append(got, m) 440 } 441 for _, m := range got { 442 m.Nack() 443 } 444 // Check that the received messages match the sent ones. 445 if diff := diffMessageSets(got, want); diff != "" { 446 t.Error(diff) 447 } 448 // The test will hang here if the messages aren't redelivered, so use a shorter timeout. 449 ctx2, cancel = context.WithTimeout(ctx, 30*time.Second) 450 defer cancel() 451 452 got = nil 453 for i := 0; i < nMessages; i++ { 454 m, err := sub.Receive(ctx2) 455 if err != nil { 456 t.Fatal(err) 457 } 458 got = append(got, m) 459 m.Ack() 460 } 461 if diff := diffMessageSets(got, want); diff != "" { 462 t.Error(diff) 463 } 464 } 465 466 func testBatching(t *testing.T, newHarness HarnessMaker) { 467 const nMessages = 12 // must be divisible by 2 468 const batchSize = nMessages / 2 469 470 // Set up. 471 ctx := context.Background() 472 h, err := newHarness(ctx, t) 473 if err != nil { 474 t.Fatal(err) 475 } 476 defer h.Close() 477 maxSendBatch, maxAckBatch := h.MaxBatchSizes() 478 479 dt, topicCleanup, err := h.CreateTopic(ctx, t.Name()) 480 if err != nil { 481 t.Fatal(err) 482 } 483 defer topicCleanup() 484 ds, subCleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 485 if err != nil { 486 t.Fatal(err) 487 } 488 defer subCleanup() 489 490 sendBatchOpts := &batcher.Options{MinBatchSize: batchSize, MaxBatchSize: batchSize} 491 // If the driver doesn't support batchSize batches, fall back to size 1. 492 if maxSendBatch != 0 && batchSize > maxSendBatch { 493 sendBatchOpts = batchSizeOne 494 } 495 topic := pubsub.NewTopic(dt, sendBatchOpts) 496 defer func() { 497 if err := topic.Shutdown(ctx); err != nil { 498 t.Error(err) 499 } 500 }() 501 ackBatchOpts := &batcher.Options{MinBatchSize: batchSize, MaxBatchSize: batchSize} 502 // If the driver doesn't support batchSize batches, fall back to size 1. 503 if maxAckBatch != 0 && batchSize > maxAckBatch { 504 ackBatchOpts = batchSizeOne 505 } 506 sub := pubsub.NewSubscription(ds, batchSizeOne, ackBatchOpts) 507 defer func() { 508 if err := sub.Shutdown(ctx); err != nil { 509 t.Error(err) 510 } 511 }() 512 513 // Publish nMessages. We have to do them asynchronously because topic.Send 514 // blocks until the message is sent, and these messages won't be sent until 515 // all batchSize are queued. 516 // Note: this test uses the same Body for each message, because the order 517 // that they appear in the SendBatch is not stable. 518 gr, grctx := errgroup.WithContext(ctx) 519 var want []*pubsub.Message 520 for i := 0; i < nMessages; i++ { 521 m := &pubsub.Message{Body: []byte("hello world")} 522 want = append(want, m) 523 gr.Go(func() error { return topic.Send(grctx, m) }) 524 } 525 if err := gr.Wait(); err != nil { 526 t.Fatal(err) 527 } 528 529 // Get the messages. 530 // The test will hang here if the messages aren't delivered, so use a shorter timeout. 531 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 532 defer cancel() 533 var got []*pubsub.Message 534 for i := 0; i < nMessages; i++ { 535 m, err := sub.Receive(ctx2) 536 if err != nil { 537 t.Fatal(err) 538 } 539 got = append(got, m) 540 m.Ack() 541 } 542 if diff := diffMessageSets(got, want); diff != "" { 543 t.Error(diff) 544 } 545 } 546 547 func testDoubleAck(t *testing.T, newHarness HarnessMaker) { 548 // Set up. 549 ctx := context.Background() 550 h, err := newHarness(ctx, t) 551 if err != nil { 552 t.Fatal(err) 553 } 554 defer h.Close() 555 dt, topicCleanup, err := h.CreateTopic(ctx, t.Name()) 556 if err != nil { 557 t.Fatal(err) 558 } 559 defer topicCleanup() 560 ds, subCleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 561 if err != nil { 562 t.Fatal(err) 563 } 564 defer subCleanup() 565 566 // Publish 3 messages. 567 for i := 0; i < 3; i++ { 568 err := dt.SendBatch(ctx, []*driver.Message{{Body: []byte(strconv.Itoa(i))}}) 569 if err != nil { 570 t.Fatal(err) 571 } 572 } 573 574 // Retrieve the messages. 575 // The test will hang here if the messages aren't delivered, so use a shorter timeout. 576 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 577 defer cancel() 578 var dms []*driver.Message 579 for len(dms) != 3 { 580 curdms, err := ds.ReceiveBatch(ctx2, 3) 581 if err != nil { 582 t.Fatal(err) 583 } 584 if err := ctx2.Err(); err != nil { 585 t.Fatalf("never received expected messages: %v", err) 586 } 587 dms = append(dms, curdms...) 588 } 589 590 // Ack the first two messages. 591 err = ds.SendAcks(ctx, []driver.AckID{dms[0].AckID, dms[1].AckID}) 592 if err != nil { 593 t.Fatal(err) 594 } 595 596 // Ack them again; this should succeed even though we've acked them before. 597 // If services return an error for this, drivers should drop them. 598 err = ds.SendAcks(ctx, []driver.AckID{dms[0].AckID, dms[1].AckID}) 599 if err != nil { 600 t.Fatal(err) 601 } 602 603 if !ds.CanNack() { 604 return 605 } 606 607 // Nack all 3 messages. This should also succeed, and the nack of the third 608 // message should take effect, so we should be able to fetch it again. 609 // Note that the other messages *may* also be re-sent, because we're nacking 610 // them here (even though we acked them earlier); it depends on service 611 // semantics and time-sensitivity. 612 err = ds.SendNacks(ctx, []driver.AckID{dms[0].AckID, dms[1].AckID, dms[2].AckID}) 613 if err != nil { 614 t.Fatal(err) 615 } 616 617 // The test will hang here if the message isn't redelivered, so use a shorter timeout. 618 ctx2, cancel = context.WithTimeout(ctx, 30*time.Second) 619 defer cancel() 620 621 // We're looking to re-receive dms[2]. 622 Loop: 623 for { 624 curdms, err := ds.ReceiveBatch(ctx2, 1) 625 if err != nil { 626 t.Fatal(err) 627 } 628 for _, curdm := range curdms { 629 if bytes.Equal(curdm.Body, dms[2].Body) { 630 // Found it! 631 break Loop 632 } 633 } 634 } 635 } 636 637 // Publish n different messages to the topic. Return the messages. 638 func publishN(ctx context.Context, t *testing.T, topic *pubsub.Topic, n int) []*pubsub.Message { 639 var ms []*pubsub.Message 640 for i := 0; i < n; i++ { 641 m := &pubsub.Message{ 642 Body: []byte(strconv.Itoa(i)), 643 Metadata: map[string]string{"a": strconv.Itoa(i)}, 644 } 645 if err := topic.Send(ctx, m); err != nil { 646 t.Fatal(err) 647 } 648 ms = append(ms, m) 649 } 650 return ms 651 } 652 653 // Receive and ack n messages from sub. 654 func receiveN(ctx context.Context, t *testing.T, sub *pubsub.Subscription, n int) []*pubsub.Message { 655 // The test will hang here if the message(s) aren't available, so use a shorter timeout. 656 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 657 defer cancel() 658 var ms []*pubsub.Message 659 for i := 0; i < n; i++ { 660 m, err := sub.Receive(ctx2) 661 if err != nil { 662 t.Fatal(err) 663 } 664 ms = append(ms, m) 665 m.Ack() 666 } 667 return ms 668 } 669 670 // Find the differences between two sets of messages. 671 func diffMessageSets(got, want []*pubsub.Message) string { 672 for _, m := range got { 673 m.LoggableID = "" 674 } 675 less := func(x, y *pubsub.Message) bool { return bytes.Compare(x.Body, y.Body) < 0 } 676 return cmp.Diff(got, want, cmpopts.SortSlices(less), cmpopts.IgnoreUnexported(pubsub.Message{})) 677 } 678 679 func testErrorOnSendToClosedTopic(t *testing.T, newHarness HarnessMaker) { 680 // Set up. 681 ctx := context.Background() 682 h, err := newHarness(ctx, t) 683 if err != nil { 684 t.Fatal(err) 685 } 686 defer h.Close() 687 688 dt, cleanup, err := h.CreateTopic(ctx, t.Name()) 689 if err != nil { 690 t.Fatal(err) 691 } 692 defer cleanup() 693 694 topic := pubsub.NewTopic(dt, batchSizeOne) 695 if err := topic.Shutdown(ctx); err != nil { 696 t.Error(err) 697 } 698 699 // Check that sending to the closed topic fails. 700 m := &pubsub.Message{} 701 if err := topic.Send(ctx, m); err == nil { 702 t.Error("topic.Send returned nil, want error") 703 } 704 if err := topic.Shutdown(ctx); err == nil { 705 t.Error("wanted error on double Shutdown") 706 } 707 } 708 709 func testErrorOnReceiveFromClosedSubscription(t *testing.T, newHarness HarnessMaker) { 710 ctx := context.Background() 711 h, err := newHarness(ctx, t) 712 if err != nil { 713 t.Fatal(err) 714 } 715 defer h.Close() 716 717 dt, cleanup, err := h.CreateTopic(ctx, t.Name()) 718 if err != nil { 719 t.Fatal(err) 720 } 721 defer cleanup() 722 723 ds, cleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 724 if err != nil { 725 t.Fatal(err) 726 } 727 defer cleanup() 728 729 sub := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 730 if err := sub.Shutdown(ctx); err != nil { 731 t.Error(err) 732 } 733 if _, err = sub.Receive(ctx); err == nil { 734 t.Error("sub.Receive returned nil, want error") 735 } 736 if err := sub.Shutdown(ctx); err == nil { 737 t.Error("wanted error on double Shutdown") 738 } 739 } 740 741 func testCancelSendReceive(t *testing.T, newHarness HarnessMaker) { 742 ctx := context.Background() 743 h, err := newHarness(ctx, t) 744 if err != nil { 745 t.Fatal(err) 746 } 747 defer h.Close() 748 topic, sub, cleanup, err := makePair(ctx, t, h) 749 if err != nil { 750 t.Fatal(err) 751 } 752 defer cleanup() 753 754 ctx, cancel := context.WithCancel(ctx) 755 cancel() 756 757 m := &pubsub.Message{} 758 if err := topic.Send(ctx, m); !isCanceled(err) { 759 t.Errorf("topic.Send returned %v (%T), want context.Canceled", err, err) 760 } 761 if _, err := sub.Receive(ctx); !isCanceled(err) { 762 t.Errorf("sub.Receive returned %v (%T), want context.Canceled", err, err) 763 } 764 765 // It would be nice to add a test that cancels an in-flight blocking Receive. 766 // However, because pubsub.Subscription.Receive repeatedly calls 767 // driver.ReceiveBatch if it returns 0 messages, it's difficult to write 768 // such a test without it being flaky for drivers with record/replay 769 // (the number of times driver.ReceiveBatch is called is timing-dependent). 770 } 771 772 func testMetadata(t *testing.T, newHarness HarnessMaker) { 773 // Set up. 774 ctx := context.Background() 775 h, err := newHarness(ctx, t) 776 if err != nil { 777 t.Fatal(err) 778 } 779 defer h.Close() 780 781 // Some services limit the number of metadata per message. 782 // Sort the escape.WeirdStrings values for record/replay consistency, 783 // then break the weird strings up into groups of at most maxMetadataKeys. 784 const maxMetadataKeys = 10 785 var weirdStrings []string 786 for _, v := range escape.WeirdStrings { 787 weirdStrings = append(weirdStrings, v) 788 } 789 sort.Slice(weirdStrings, func(i, j int) bool { return weirdStrings[i] < weirdStrings[j] }) 790 791 weirdMetaDataGroups := []map[string]string{{}} 792 i := 0 793 for _, k := range weirdStrings { 794 weirdMetaDataGroups[i][k] = k 795 if len(weirdMetaDataGroups[i]) == maxMetadataKeys { 796 weirdMetaDataGroups = append(weirdMetaDataGroups, map[string]string{}) 797 i++ 798 } 799 } 800 801 topic, sub, cleanup, err := makePair(ctx, t, h) 802 if err != nil { 803 t.Fatal(err) 804 } 805 defer cleanup() 806 807 for _, wm := range weirdMetaDataGroups { 808 m := &pubsub.Message{ 809 Body: []byte("hello world"), 810 Metadata: wm, 811 } 812 if err := topic.Send(ctx, m); err != nil { 813 t.Fatal(err) 814 } 815 816 // The test will hang here if the messages aren't delivered, so use a shorter timeout. 817 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 818 defer cancel() 819 m, err = sub.Receive(ctx2) 820 if err != nil { 821 t.Fatal(err) 822 } 823 m.Ack() 824 825 if diff := cmp.Diff(m.Metadata, wm); diff != "" { 826 t.Fatalf("got\n%v\nwant\n%v\ndiff\n%s", m.Metadata, wm, diff) 827 } 828 } 829 830 // Verify that non-UTF8 strings in metadata key or value fail. 831 m := &pubsub.Message{ 832 Body: []byte("hello world"), 833 Metadata: map[string]string{escape.NonUTF8String: "bar"}, 834 } 835 if err := topic.Send(ctx, m); err == nil { 836 t.Error("got nil error, expected error for using non-UTF8 string as metadata key") 837 } 838 m.Metadata = map[string]string{"foo": escape.NonUTF8String} 839 if err := topic.Send(ctx, m); err == nil { 840 t.Error("got nil error, expected error for using non-UTF8 string as metadata value") 841 } 842 } 843 844 func testNonUTF8MessageBody(t *testing.T, newHarness HarnessMaker) { 845 // Set up. 846 ctx := context.Background() 847 h, err := newHarness(ctx, t) 848 if err != nil { 849 t.Fatal(err) 850 } 851 defer h.Close() 852 853 topic, sub, cleanup, err := makePair(ctx, t, h) 854 if err != nil { 855 t.Fatal(err) 856 } 857 defer cleanup() 858 859 // Sort the WeirdStrings map for record/replay consistency. 860 var weirdStrings [][]string // [0] = key, [1] = value 861 for k, v := range escape.WeirdStrings { 862 weirdStrings = append(weirdStrings, []string{k, v}) 863 } 864 sort.Slice(weirdStrings, func(i, j int) bool { return weirdStrings[i][0] < weirdStrings[j][0] }) 865 866 // Construct a message body with the weird strings and some non-UTF-8 bytes. 867 var body []byte 868 for _, v := range weirdStrings { 869 body = append(body, []byte(v[1])...) 870 } 871 body = append(body, []byte(escape.NonUTF8String)...) 872 m := &pubsub.Message{Body: body} 873 874 if err := topic.Send(ctx, m); err != nil { 875 t.Fatal(err) 876 } 877 // The test will hang here if the messages aren't delivered, so use a shorter timeout. 878 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 879 defer cancel() 880 m, err = sub.Receive(ctx2) 881 if err != nil { 882 t.Fatal(err) 883 } 884 m.Ack() 885 886 if diff := cmp.Diff(m.Body, body); diff != "" { 887 t.Fatalf("got\n%v\nwant\n%v\ndiff\n%s", m.Body, body, diff) 888 } 889 } 890 891 func isCanceled(err error) bool { 892 if err == context.Canceled { 893 return true 894 } 895 if cerr, ok := err.(*retry.ContextError); ok { 896 return cerr.CtxErr == context.Canceled 897 } 898 return gcerrors.Code(err) == gcerrors.Canceled 899 } 900 901 func makePair(ctx context.Context, t *testing.T, h Harness) (*pubsub.Topic, *pubsub.Subscription, func(), error) { 902 dt, topicCleanup, err := h.CreateTopic(ctx, t.Name()) 903 if err != nil { 904 return nil, nil, nil, err 905 } 906 ds, subCleanup, err := h.CreateSubscription(ctx, dt, t.Name()) 907 if err != nil { 908 topicCleanup() 909 return nil, nil, nil, err 910 } 911 topic := pubsub.NewTopic(dt, batchSizeOne) 912 sub := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 913 cleanup := func() { 914 if err := topic.Shutdown(ctx); err != nil { 915 t.Error(err) 916 } 917 if err := sub.Shutdown(ctx); err != nil { 918 t.Error(err) 919 } 920 subCleanup() 921 topicCleanup() 922 } 923 return topic, sub, cleanup, nil 924 } 925 926 // testAs tests the various As functions, using AsTest. 927 func testAs(t *testing.T, newHarness HarnessMaker, st AsTest) { 928 ctx := context.Background() 929 h, err := newHarness(ctx, t) 930 if err != nil { 931 t.Fatal(err) 932 } 933 defer h.Close() 934 topic, sub, cleanup, err := makePair(ctx, t, h) 935 if err != nil { 936 t.Fatal(err) 937 } 938 defer cleanup() 939 940 if err := st.TopicCheck(topic); err != nil { 941 t.Error(err) 942 } 943 944 if err := st.SubscriptionCheck(sub); err != nil { 945 t.Error(err) 946 } 947 948 msg := &pubsub.Message{ 949 Body: []byte("x"), 950 BeforeSend: st.BeforeSend, 951 AfterSend: st.AfterSend, 952 } 953 if err := topic.Send(ctx, msg); err != nil { 954 t.Fatal(err) 955 } 956 // The test will hang here if the messages aren't delivered, so use a shorter timeout. 957 ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) 958 defer cancel() 959 m, err := sub.Receive(ctx2) 960 if err != nil { 961 t.Fatal(err) 962 } 963 if err := st.MessageCheck(m); err != nil { 964 t.Error(err) 965 } 966 m.Ack() 967 968 // Make a nonexistent topic and try to to send on it, to get an error we can 969 // use to call TopicErrorCheck. 970 dt, err := h.MakeNonexistentTopic(ctx) 971 if err != nil { 972 t.Fatal(err) 973 } 974 nonexistentTopic := pubsub.NewTopic(dt, batchSizeOne) 975 defer func() { 976 if err := nonexistentTopic.Shutdown(ctx); err != nil { 977 t.Error(err) 978 } 979 }() 980 topicErr := nonexistentTopic.Send(ctx, &pubsub.Message{}) 981 if topicErr == nil || gcerrors.Code(topicErr) != gcerrors.NotFound { 982 t.Errorf("got error %v sending to nonexistent topic, want Code=NotFound", topicErr) 983 } else if err := st.TopicErrorCheck(topic, topicErr); err != nil { 984 t.Error(err) 985 } 986 987 // Make a nonexistent subscription and try to receive from it, to get an error 988 // we can use to call SubscriptionErrorCheck. 989 ds, cleanup, err := h.MakeNonexistentSubscription(ctx) 990 if err != nil { 991 t.Fatal(err) 992 } 993 defer cleanup() 994 nonExistentSub := pubsub.NewSubscription(ds, batchSizeOne, batchSizeOne) 995 defer func() { 996 if err := nonExistentSub.Shutdown(ctx); err != nil { 997 t.Error(err) 998 } 999 }() 1000 1001 // The test will hang here if Receive doesn't fail quickly, so set a shorter timeout. 1002 ctx2, cancel = context.WithTimeout(ctx, 30*time.Second) 1003 defer cancel() 1004 _, subErr := nonExistentSub.Receive(ctx2) 1005 if subErr == nil || ctx2.Err() != nil || gcerrors.Code(subErr) != gcerrors.NotFound { 1006 t.Errorf("got error %v receiving from nonexistent subscription, want Code=NotFound", subErr) 1007 } else if err := st.SubscriptionErrorCheck(nonExistentSub, subErr); err != nil { 1008 t.Error(err) 1009 } 1010 } 1011 1012 // Publishes a large number of messages to topic concurrently, and then times 1013 // how long it takes to send (if timeSend is true) or receive (if timeSend 1014 // is false) them all. 1015 func benchmark(b *testing.B, topic *pubsub.Topic, sub *pubsub.Subscription, timeSend bool) { 1016 attrs := map[string]string{"label": "value"} 1017 body := []byte("hello, world") 1018 const ( 1019 nMessages = 10000 1020 concurrencySend = 100 1021 concurrencyReceive = 100 1022 ) 1023 if nMessages%concurrencySend != 0 || nMessages%concurrencyReceive != 0 { 1024 b.Fatal("nMessages must be divisible by # of sending/receiving goroutines") 1025 } 1026 b.ResetTimer() 1027 for i := 0; i < b.N; i++ { 1028 if !timeSend { 1029 b.StopTimer() 1030 } 1031 if err := publishNConcurrently(topic, nMessages, concurrencySend, attrs, body); err != nil { 1032 b.Fatalf("publishing: %v", err) 1033 } 1034 b.Logf("published %d messages", nMessages) 1035 if timeSend { 1036 b.StopTimer() 1037 } else { 1038 b.StartTimer() 1039 } 1040 if err := receiveNConcurrently(sub, nMessages, concurrencyReceive); err != nil { 1041 b.Fatalf("receiving: %v", err) 1042 } 1043 b.SetBytes(nMessages * 1e6) 1044 b.Log("MB/s is actually number of messages received per second") 1045 if timeSend { 1046 b.StartTimer() 1047 } 1048 } 1049 } 1050 1051 func publishNConcurrently(topic *pubsub.Topic, nMessages, nGoroutines int, attrs map[string]string, body []byte) error { 1052 return runConcurrently(nMessages, nGoroutines, func(ctx context.Context) error { 1053 return topic.Send(ctx, &pubsub.Message{Metadata: attrs, Body: body}) 1054 }) 1055 } 1056 1057 func receiveNConcurrently(sub *pubsub.Subscription, nMessages, nGoroutines int) error { 1058 return runConcurrently(nMessages, nGoroutines, func(ctx context.Context) error { 1059 m, err := sub.Receive(ctx) 1060 if err != nil { 1061 return err 1062 } 1063 m.Ack() 1064 return nil 1065 }) 1066 } 1067 1068 // Call function f n times concurrently, using g goroutines. g must divide n. 1069 // Wait until all calls complete. If any fail, cancel the remaining ones. 1070 func runConcurrently(n, g int, f func(context.Context) error) error { 1071 gr, ctx := errgroup.WithContext(context.Background()) 1072 ng := n / g 1073 for i := 0; i < g; i++ { 1074 gr.Go(func() error { 1075 for j := 0; j < ng; j++ { 1076 if err := f(ctx); err != nil { 1077 return err 1078 } 1079 } 1080 return nil 1081 }) 1082 } 1083 return gr.Wait() 1084 }