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  }