github.com/hoveychen/kafka-go@v0.4.42/writer_test.go (about) 1 package kafka 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "math" 9 "strconv" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/hoveychen/kafka-go/sasl/plain" 16 ) 17 18 func TestBatchQueue(t *testing.T) { 19 tests := []struct { 20 scenario string 21 function func(*testing.T) 22 }{ 23 { 24 scenario: "the remaining items in a queue can be gotten after closing", 25 function: testBatchQueueGetWorksAfterClose, 26 }, 27 { 28 scenario: "putting into a closed queue fails", 29 function: testBatchQueuePutAfterCloseFails, 30 }, 31 { 32 scenario: "putting into a queue awakes a goroutine in a get call", 33 function: testBatchQueuePutWakesSleepingGetter, 34 }, 35 } 36 37 for _, test := range tests { 38 testFunc := test.function 39 t.Run(test.scenario, func(t *testing.T) { 40 t.Parallel() 41 testFunc(t) 42 }) 43 } 44 } 45 46 func testBatchQueuePutWakesSleepingGetter(t *testing.T) { 47 bq := newBatchQueue(10) 48 var wg sync.WaitGroup 49 ready := make(chan struct{}) 50 var batch *writeBatch 51 wg.Add(1) 52 go func() { 53 defer wg.Done() 54 close(ready) 55 batch = bq.Get() 56 }() 57 <-ready 58 bq.Put(newWriteBatch(time.Now(), time.Hour*100)) 59 wg.Wait() 60 if batch == nil { 61 t.Fatal("got nil batch") 62 } 63 } 64 65 func testBatchQueuePutAfterCloseFails(t *testing.T) { 66 bq := newBatchQueue(10) 67 bq.Close() 68 if put := bq.Put(newWriteBatch(time.Now(), time.Hour*100)); put { 69 t.Fatal("put batch into closed queue") 70 } 71 } 72 73 func testBatchQueueGetWorksAfterClose(t *testing.T) { 74 bq := newBatchQueue(10) 75 enqueueBatches := []*writeBatch{ 76 newWriteBatch(time.Now(), time.Hour*100), 77 newWriteBatch(time.Now(), time.Hour*100), 78 } 79 80 for _, batch := range enqueueBatches { 81 put := bq.Put(batch) 82 if !put { 83 t.Fatal("failed to put batch into queue") 84 } 85 } 86 87 bq.Close() 88 89 batchesGotten := 0 90 for batchesGotten != 2 { 91 dequeueBatch := bq.Get() 92 if dequeueBatch == nil { 93 t.Fatalf("no batch returned from get") 94 } 95 batchesGotten++ 96 } 97 } 98 99 func TestWriter(t *testing.T) { 100 tests := []struct { 101 scenario string 102 function func(*testing.T) 103 }{ 104 { 105 scenario: "closing a writer right after creating it returns promptly with no error", 106 function: testWriterClose, 107 }, 108 109 { 110 scenario: "writing 1 message through a writer using round-robin balancing produces 1 message to the first partition", 111 function: testWriterRoundRobin1, 112 }, 113 114 { 115 scenario: "running out of max attempts should return an error", 116 function: testWriterMaxAttemptsErr, 117 }, 118 119 { 120 scenario: "writing a message larger then the max bytes should return an error", 121 function: testWriterMaxBytes, 122 }, 123 124 { 125 scenario: "writing a batch of message based on batch byte size", 126 function: testWriterBatchBytes, 127 }, 128 129 { 130 scenario: "writing a batch of messages", 131 function: testWriterBatchSize, 132 }, 133 134 { 135 scenario: "writing messages with a small batch byte size", 136 function: testWriterSmallBatchBytes, 137 }, 138 { 139 scenario: "writing messages with headers", 140 function: testWriterBatchBytesHeaders, 141 }, 142 { 143 scenario: "setting a non default balancer on the writer", 144 function: testWriterSetsRightBalancer, 145 }, 146 { 147 scenario: "setting RequiredAcks to None in Writer does not cause a panic", 148 function: testWriterRequiredAcksNone, 149 }, 150 { 151 scenario: "writing messages to multiple topics", 152 function: testWriterMultipleTopics, 153 }, 154 { 155 scenario: "writing messages without specifying a topic", 156 function: testWriterMissingTopic, 157 }, 158 { 159 scenario: "specifying topic for message when already set for writer", 160 function: testWriterUnexpectedMessageTopic, 161 }, 162 { 163 scenario: "writing a message to an invalid partition", 164 function: testWriterInvalidPartition, 165 }, 166 { 167 scenario: "writing a message to a non-existent topic creates the topic", 168 function: testWriterAutoCreateTopic, 169 }, 170 { 171 scenario: "terminates on an attempt to write a message to a nonexistent topic", 172 function: testWriterTerminateMissingTopic, 173 }, 174 { 175 scenario: "writing a message with SASL Plain authentication", 176 function: testWriterSasl, 177 }, 178 { 179 scenario: "test default configuration values", 180 function: testWriterDefaults, 181 }, 182 { 183 scenario: "test write message with writer data", 184 function: testWriteMessageWithWriterData, 185 }, 186 } 187 188 for _, test := range tests { 189 testFunc := test.function 190 t.Run(test.scenario, func(t *testing.T) { 191 t.Parallel() 192 testFunc(t) 193 }) 194 } 195 } 196 197 func newTestWriter(config WriterConfig) *Writer { 198 if len(config.Brokers) == 0 { 199 config.Brokers = []string{"localhost:9092"} 200 } 201 return NewWriter(config) 202 } 203 204 func testWriterClose(t *testing.T) { 205 const topic = "test-writer-0" 206 createTopic(t, topic, 1) 207 defer deleteTopic(t, topic) 208 209 w := newTestWriter(WriterConfig{ 210 Topic: topic, 211 }) 212 213 if err := w.Close(); err != nil { 214 t.Error(err) 215 } 216 } 217 218 func testWriterRequiredAcksNone(t *testing.T) { 219 topic := makeTopic() 220 createTopic(t, topic, 1) 221 defer deleteTopic(t, topic) 222 223 transport := &Transport{} 224 defer transport.CloseIdleConnections() 225 226 writer := &Writer{ 227 Addr: TCP("localhost:9092"), 228 Topic: topic, 229 Balancer: &RoundRobin{}, 230 RequiredAcks: RequireNone, 231 Transport: transport, 232 } 233 defer writer.Close() 234 235 msg := Message{ 236 Key: []byte("ThisIsAKey"), 237 Value: []byte("Test message for required acks test"), 238 } 239 240 err := writer.WriteMessages(context.Background(), msg) 241 if err != nil { 242 t.Fatal(err) 243 } 244 } 245 246 func testWriterSetsRightBalancer(t *testing.T) { 247 const topic = "test-writer-1" 248 balancer := &CRC32Balancer{} 249 w := newTestWriter(WriterConfig{ 250 Topic: topic, 251 Balancer: balancer, 252 }) 253 defer w.Close() 254 255 if w.Balancer != balancer { 256 t.Errorf("Balancer not set correctly") 257 } 258 } 259 260 func testWriterRoundRobin1(t *testing.T) { 261 const topic = "test-writer-1" 262 createTopic(t, topic, 1) 263 defer deleteTopic(t, topic) 264 265 offset, err := readOffset(topic, 0) 266 if err != nil { 267 t.Fatal(err) 268 } 269 270 w := newTestWriter(WriterConfig{ 271 Topic: topic, 272 Balancer: &RoundRobin{}, 273 }) 274 defer w.Close() 275 276 if err := w.WriteMessages(context.Background(), Message{ 277 Value: []byte("Hello World!"), 278 }); err != nil { 279 t.Error(err) 280 return 281 } 282 283 msgs, err := readPartition(topic, 0, offset) 284 if err != nil { 285 t.Error("error reading partition", err) 286 return 287 } 288 289 if len(msgs) != 1 { 290 t.Error("bad messages in partition", msgs) 291 return 292 } 293 294 for _, m := range msgs { 295 if string(m.Value) != "Hello World!" { 296 t.Error("bad messages in partition", msgs) 297 break 298 } 299 } 300 } 301 302 func TestValidateWriter(t *testing.T) { 303 tests := []struct { 304 config WriterConfig 305 errorOccured bool 306 }{ 307 {config: WriterConfig{}, errorOccured: true}, 308 {config: WriterConfig{Brokers: []string{"broker1", "broker2"}}, errorOccured: false}, 309 {config: WriterConfig{Brokers: []string{"broker1"}, Topic: "topic1"}, errorOccured: false}, 310 } 311 for _, test := range tests { 312 err := test.config.Validate() 313 if test.errorOccured && err == nil { 314 t.Fail() 315 } 316 if !test.errorOccured && err != nil { 317 t.Fail() 318 } 319 } 320 } 321 322 func testWriterMaxAttemptsErr(t *testing.T) { 323 topic := makeTopic() 324 createTopic(t, topic, 1) 325 defer deleteTopic(t, topic) 326 327 w := newTestWriter(WriterConfig{ 328 Brokers: []string{"localhost:9999"}, // nothing is listening here 329 Topic: topic, 330 MaxAttempts: 3, 331 Balancer: &RoundRobin{}, 332 }) 333 defer w.Close() 334 335 if err := w.WriteMessages(context.Background(), Message{ 336 Value: []byte("Hello World!"), 337 }); err == nil { 338 t.Error("expected error") 339 return 340 } 341 } 342 343 func testWriterMaxBytes(t *testing.T) { 344 topic := makeTopic() 345 createTopic(t, topic, 1) 346 defer deleteTopic(t, topic) 347 348 w := newTestWriter(WriterConfig{ 349 Topic: topic, 350 BatchBytes: 25, 351 }) 352 defer w.Close() 353 354 if err := w.WriteMessages(context.Background(), Message{ 355 Value: []byte("Hi"), 356 }); err != nil { 357 t.Error(err) 358 return 359 } 360 361 firstMsg := []byte("Hello World!") 362 secondMsg := []byte("LeftOver!") 363 msgs := []Message{ 364 { 365 Value: firstMsg, 366 }, 367 { 368 Value: secondMsg, 369 }, 370 } 371 if err := w.WriteMessages(context.Background(), msgs...); err == nil { 372 t.Error("expected error") 373 return 374 } else if err != nil { 375 var e MessageTooLargeError 376 switch { 377 case errors.As(err, &e): 378 if string(e.Message.Value) != string(firstMsg) { 379 t.Errorf("unxpected returned message. Expected: %s, Got %s", firstMsg, e.Message.Value) 380 return 381 } 382 if len(e.Remaining) != 1 { 383 t.Error("expected remaining errors; found none") 384 return 385 } 386 if string(e.Remaining[0].Value) != string(secondMsg) { 387 t.Errorf("unxpected returned message. Expected: %s, Got %s", secondMsg, e.Message.Value) 388 return 389 } 390 391 default: 392 t.Errorf("unexpected error: %s", err) 393 return 394 } 395 } 396 } 397 398 // readOffset gets the latest offset for the given topic/partition. 399 func readOffset(topic string, partition int) (offset int64, err error) { 400 var conn *Conn 401 402 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 403 defer cancel() 404 405 if conn, err = DialLeader(ctx, "tcp", "localhost:9092", topic, partition); err != nil { 406 err = fmt.Errorf("readOffset, DialLeader: %w", err) 407 return 408 } 409 defer conn.Close() 410 411 offset, err = conn.ReadLastOffset() 412 if err != nil { 413 err = fmt.Errorf("readOffset, conn.ReadLastOffset: %w", err) 414 } 415 return 416 } 417 418 func readPartition(topic string, partition int, offset int64) (msgs []Message, err error) { 419 var conn *Conn 420 421 if conn, err = DialLeader(context.Background(), "tcp", "localhost:9092", topic, partition); err != nil { 422 return 423 } 424 defer conn.Close() 425 426 conn.Seek(offset, SeekAbsolute) 427 conn.SetReadDeadline(time.Now().Add(10 * time.Second)) 428 batch := conn.ReadBatch(0, 1000000000) 429 defer batch.Close() 430 431 for { 432 var msg Message 433 434 if msg, err = batch.ReadMessage(); err != nil { 435 if errors.Is(err, io.EOF) { 436 err = nil 437 } 438 return 439 } 440 441 msgs = append(msgs, msg) 442 } 443 } 444 445 func testWriterBatchBytes(t *testing.T) { 446 topic := makeTopic() 447 createTopic(t, topic, 1) 448 defer deleteTopic(t, topic) 449 450 offset, err := readOffset(topic, 0) 451 if err != nil { 452 t.Fatal(err) 453 } 454 455 w := newTestWriter(WriterConfig{ 456 Topic: topic, 457 BatchBytes: 50, 458 BatchTimeout: math.MaxInt32 * time.Second, 459 Balancer: &RoundRobin{}, 460 }) 461 defer w.Close() 462 463 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 464 defer cancel() 465 if err := w.WriteMessages(ctx, []Message{ 466 {Value: []byte("M0")}, // 25 Bytes 467 {Value: []byte("M1")}, // 25 Bytes 468 {Value: []byte("M2")}, // 25 Bytes 469 {Value: []byte("M3")}, // 25 Bytes 470 }...); err != nil { 471 t.Error(err) 472 return 473 } 474 475 if w.Stats().Writes != 2 { 476 t.Error("didn't create expected batches") 477 return 478 } 479 msgs, err := readPartition(topic, 0, offset) 480 if err != nil { 481 t.Error("error reading partition", err) 482 return 483 } 484 485 if len(msgs) != 4 { 486 t.Error("bad messages in partition", msgs) 487 return 488 } 489 490 for i, m := range msgs { 491 if string(m.Value) == "M"+strconv.Itoa(i) { 492 continue 493 } 494 t.Error("bad messages in partition", string(m.Value)) 495 } 496 } 497 498 func testWriterBatchSize(t *testing.T) { 499 topic := makeTopic() 500 createTopic(t, topic, 1) 501 defer deleteTopic(t, topic) 502 503 offset, err := readOffset(topic, 0) 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 w := newTestWriter(WriterConfig{ 509 Topic: topic, 510 BatchSize: 2, 511 BatchTimeout: math.MaxInt32 * time.Second, 512 Balancer: &RoundRobin{}, 513 }) 514 defer w.Close() 515 516 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 517 defer cancel() 518 if err := w.WriteMessages(ctx, []Message{ 519 {Value: []byte("Hi")}, // 24 Bytes 520 {Value: []byte("By")}, // 24 Bytes 521 }...); err != nil { 522 t.Error(err) 523 return 524 } 525 526 if w.Stats().Writes > 1 { 527 t.Error("didn't batch messages") 528 return 529 } 530 msgs, err := readPartition(topic, 0, offset) 531 if err != nil { 532 t.Error("error reading partition", err) 533 return 534 } 535 536 if len(msgs) != 2 { 537 t.Error("bad messages in partition", msgs) 538 return 539 } 540 541 for _, m := range msgs { 542 if string(m.Value) == "Hi" || string(m.Value) == "By" { 543 continue 544 } 545 t.Error("bad messages in partition", msgs) 546 } 547 } 548 549 func testWriterSmallBatchBytes(t *testing.T) { 550 topic := makeTopic() 551 createTopic(t, topic, 1) 552 defer deleteTopic(t, topic) 553 554 offset, err := readOffset(topic, 0) 555 if err != nil { 556 t.Fatal(err) 557 } 558 559 w := newTestWriter(WriterConfig{ 560 Topic: topic, 561 BatchBytes: 25, 562 BatchTimeout: 50 * time.Millisecond, 563 Balancer: &RoundRobin{}, 564 }) 565 defer w.Close() 566 567 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 568 defer cancel() 569 if err := w.WriteMessages(ctx, []Message{ 570 {Value: []byte("Hi")}, // 24 Bytes 571 {Value: []byte("By")}, // 24 Bytes 572 }...); err != nil { 573 t.Error(err) 574 return 575 } 576 ws := w.Stats() 577 if ws.Writes != 2 { 578 t.Error("didn't batch messages; Writes: ", ws.Writes) 579 return 580 } 581 msgs, err := readPartition(topic, 0, offset) 582 if err != nil { 583 t.Error("error reading partition", err) 584 return 585 } 586 587 if len(msgs) != 2 { 588 t.Error("bad messages in partition", msgs) 589 return 590 } 591 592 for _, m := range msgs { 593 if string(m.Value) == "Hi" || string(m.Value) == "By" { 594 continue 595 } 596 t.Error("bad messages in partition", msgs) 597 } 598 } 599 600 func testWriterBatchBytesHeaders(t *testing.T) { 601 topic := makeTopic() 602 createTopic(t, topic, 1) 603 defer deleteTopic(t, topic) 604 605 offset, err := readOffset(topic, 0) 606 if err != nil { 607 t.Fatal(err) 608 } 609 610 w := newTestWriter(WriterConfig{ 611 Topic: topic, 612 BatchBytes: 100, 613 BatchTimeout: 50 * time.Millisecond, 614 Balancer: &RoundRobin{}, 615 }) 616 defer w.Close() 617 618 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 619 defer cancel() 620 if err := w.WriteMessages(ctx, []Message{ 621 { 622 Value: []byte("Hello World 1"), 623 Headers: []Header{ 624 {Key: "User-Agent", Value: []byte("abc/xyz")}, 625 }, 626 }, 627 { 628 Value: []byte("Hello World 2"), 629 Headers: []Header{ 630 {Key: "User-Agent", Value: []byte("abc/xyz")}, 631 }, 632 }, 633 }...); err != nil { 634 t.Error(err) 635 return 636 } 637 ws := w.Stats() 638 if ws.Writes != 2 { 639 t.Error("didn't batch messages; Writes: ", ws.Writes) 640 return 641 } 642 msgs, err := readPartition(topic, 0, offset) 643 if err != nil { 644 t.Error("error reading partition", err) 645 return 646 } 647 648 if len(msgs) != 2 { 649 t.Error("bad messages in partition", msgs) 650 return 651 } 652 653 for _, m := range msgs { 654 if strings.HasPrefix(string(m.Value), "Hello World") { 655 continue 656 } 657 t.Error("bad messages in partition", msgs) 658 } 659 } 660 661 func testWriterMultipleTopics(t *testing.T) { 662 topic1 := makeTopic() 663 createTopic(t, topic1, 1) 664 defer deleteTopic(t, topic1) 665 666 offset1, err := readOffset(topic1, 0) 667 if err != nil { 668 t.Fatal(err) 669 } 670 671 topic2 := makeTopic() 672 createTopic(t, topic2, 1) 673 defer deleteTopic(t, topic2) 674 675 offset2, err := readOffset(topic2, 0) 676 if err != nil { 677 t.Fatal(err) 678 } 679 680 w := newTestWriter(WriterConfig{ 681 Balancer: &RoundRobin{}, 682 }) 683 defer w.Close() 684 685 msg1 := Message{Topic: topic1, Value: []byte("Hello")} 686 msg2 := Message{Topic: topic2, Value: []byte("World")} 687 688 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 689 defer cancel() 690 if err := w.WriteMessages(ctx, msg1, msg2); err != nil { 691 t.Error(err) 692 return 693 } 694 ws := w.Stats() 695 if ws.Writes != 2 { 696 t.Error("didn't batch messages; Writes: ", ws.Writes) 697 return 698 } 699 700 msgs1, err := readPartition(topic1, 0, offset1) 701 if err != nil { 702 t.Error("error reading partition", err) 703 return 704 } 705 if len(msgs1) != 1 { 706 t.Error("bad messages in partition", msgs1) 707 return 708 } 709 if string(msgs1[0].Value) != "Hello" { 710 t.Error("bad message in partition", msgs1) 711 } 712 713 msgs2, err := readPartition(topic2, 0, offset2) 714 if err != nil { 715 t.Error("error reading partition", err) 716 return 717 } 718 if len(msgs2) != 1 { 719 t.Error("bad messages in partition", msgs2) 720 return 721 } 722 if string(msgs2[0].Value) != "World" { 723 t.Error("bad message in partition", msgs2) 724 } 725 } 726 727 func testWriterMissingTopic(t *testing.T) { 728 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 729 defer cancel() 730 731 w := newTestWriter(WriterConfig{ 732 // no topic 733 Balancer: &RoundRobin{}, 734 }) 735 defer w.Close() 736 737 msg := Message{Value: []byte("Hello World")} // no topic 738 739 if err := w.WriteMessages(ctx, msg); err == nil { 740 t.Error("expected error") 741 return 742 } 743 } 744 745 func testWriterInvalidPartition(t *testing.T) { 746 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 747 defer cancel() 748 749 topic := makeTopic() 750 createTopic(t, topic, 1) 751 defer deleteTopic(t, topic) 752 753 w := newTestWriter(WriterConfig{ 754 Topic: topic, 755 MaxAttempts: 1, // only try once to get the error back immediately 756 Balancer: &staticBalancer{partition: -1}, // intentionally invalid partition 757 }) 758 defer w.Close() 759 760 msg := Message{ 761 Value: []byte("Hello World!"), 762 } 763 764 // this call should return an error and not panic (see issue #517) 765 if err := w.WriteMessages(ctx, msg); err == nil { 766 t.Fatal("expected error attempting to write message") 767 } 768 } 769 770 func testWriterUnexpectedMessageTopic(t *testing.T) { 771 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 772 defer cancel() 773 774 topic := makeTopic() 775 createTopic(t, topic, 1) 776 defer deleteTopic(t, topic) 777 778 w := newTestWriter(WriterConfig{ 779 Topic: topic, 780 Balancer: &RoundRobin{}, 781 }) 782 defer w.Close() 783 784 msg := Message{Topic: "should-fail", Value: []byte("Hello World")} 785 786 if err := w.WriteMessages(ctx, msg); err == nil { 787 t.Error("expected error") 788 return 789 } 790 } 791 792 func testWriteMessageWithWriterData(t *testing.T) { 793 topic := makeTopic() 794 createTopic(t, topic, 1) 795 defer deleteTopic(t, topic) 796 w := newTestWriter(WriterConfig{ 797 Topic: topic, 798 Balancer: &RoundRobin{}, 799 }) 800 defer w.Close() 801 802 index := 0 803 w.Completion = func(messages []Message, err error) { 804 if err != nil { 805 t.Errorf("unexpected error %v", err) 806 } 807 808 for _, msg := range messages { 809 meta := msg.WriterData.(int) 810 if index != meta { 811 t.Errorf("metadata is not correct, index = %d, writerData = %d", index, meta) 812 } 813 index += 1 814 } 815 } 816 817 msg := Message{Key: []byte("key"), Value: []byte("Hello World")} 818 for i := 0; i < 5; i++ { 819 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 820 defer cancel() 821 822 msg.WriterData = i 823 err := w.WriteMessages(ctx, msg) 824 if err != nil { 825 t.Errorf("unexpected error %v", err) 826 } 827 } 828 829 } 830 831 func testWriterAutoCreateTopic(t *testing.T) { 832 topic := makeTopic() 833 // Assume it's going to get created. 834 defer deleteTopic(t, topic) 835 836 w := newTestWriter(WriterConfig{ 837 Topic: topic, 838 Balancer: &RoundRobin{}, 839 }) 840 w.AllowAutoTopicCreation = true 841 defer w.Close() 842 843 msg := Message{Key: []byte("key"), Value: []byte("Hello World")} 844 845 var err error 846 const retries = 5 847 for i := 0; i < retries; i++ { 848 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 849 defer cancel() 850 err = w.WriteMessages(ctx, msg) 851 if errors.Is(err, LeaderNotAvailable) || errors.Is(err, context.DeadlineExceeded) { 852 time.Sleep(time.Millisecond * 250) 853 continue 854 } 855 856 if err != nil { 857 t.Errorf("unexpected error %v", err) 858 return 859 } 860 } 861 if err != nil { 862 t.Errorf("unable to create topic %v", err) 863 } 864 } 865 866 func testWriterTerminateMissingTopic(t *testing.T) { 867 topic := makeTopic() 868 869 transport := &Transport{} 870 defer transport.CloseIdleConnections() 871 872 writer := &Writer{ 873 Addr: TCP("localhost:9092"), 874 Topic: topic, 875 Balancer: &RoundRobin{}, 876 RequiredAcks: RequireNone, 877 AllowAutoTopicCreation: false, 878 Transport: transport, 879 } 880 defer writer.Close() 881 882 msg := Message{Value: []byte("FooBar")} 883 884 if err := writer.WriteMessages(context.Background(), msg); err == nil { 885 t.Fatal("Kafka error [3] UNKNOWN_TOPIC_OR_PARTITION is expected") 886 return 887 } 888 } 889 890 func testWriterSasl(t *testing.T) { 891 topic := makeTopic() 892 defer deleteTopic(t, topic) 893 dialer := &Dialer{ 894 Timeout: 10 * time.Second, 895 SASLMechanism: plain.Mechanism{ 896 Username: "adminplain", 897 Password: "admin-secret", 898 }, 899 } 900 901 w := newTestWriter(WriterConfig{ 902 Dialer: dialer, 903 Topic: topic, 904 Brokers: []string{"localhost:9093"}, 905 }) 906 907 w.AllowAutoTopicCreation = true 908 909 defer w.Close() 910 911 msg := Message{Key: []byte("key"), Value: []byte("Hello World")} 912 913 var err error 914 const retries = 5 915 for i := 0; i < retries; i++ { 916 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 917 defer cancel() 918 err = w.WriteMessages(ctx, msg) 919 if errors.Is(err, LeaderNotAvailable) || errors.Is(err, context.DeadlineExceeded) { 920 time.Sleep(time.Millisecond * 250) 921 continue 922 } 923 924 if err != nil { 925 t.Errorf("unexpected error %v", err) 926 return 927 } 928 } 929 if err != nil { 930 t.Errorf("unable to create topic %v", err) 931 } 932 } 933 934 func testWriterDefaults(t *testing.T) { 935 w := &Writer{} 936 defer w.Close() 937 938 if w.writeBackoffMin() != 100*time.Millisecond { 939 t.Error("Incorrect default min write backoff delay") 940 } 941 942 if w.writeBackoffMax() != 1*time.Second { 943 t.Error("Incorrect default max write backoff delay") 944 } 945 } 946 947 type staticBalancer struct { 948 partition int 949 } 950 951 func (b *staticBalancer) Balance(_ Message, partitions ...int) int { 952 return b.partition 953 }