github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/topic/topicwriterinternal/writer_reconnector_test.go (about) 1 package topicwriterinternal 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "context" 7 "errors" 8 "fmt" 9 "io" 10 "sort" 11 "sync" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/stretchr/testify/require" 17 "go.uber.org/mock/gomock" 18 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/empty" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicwriter" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawydb" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 26 ) 27 28 var testCommonEncoders = NewEncoderMap() 29 30 func TestWriterImpl_AutoSeq(t *testing.T) { 31 t.Run("OK", func(t *testing.T) { 32 ctx := xtest.Context(t) 33 w := newWriterReconnectorStopped(newWriterReconnectorConfig( 34 WithAutoSetSeqNo(true), 35 WithAutosetCreatedTime(false), 36 )) 37 w.firstConnectionHandled.Store(true) 38 39 lastSeqNo := int64(16) 40 w.lastSeqNo = lastSeqNo 41 42 var wg sync.WaitGroup 43 fWrite := func(num int) { 44 defer wg.Done() 45 46 msgs := newTestMessages(0) 47 msgs[0].CreatedAt = time.Unix(int64(num), 0) 48 require.NoError(t, w.Write(ctx, msgs)) 49 } 50 51 const messCount = 1000 52 wg.Add(messCount) 53 for i := 0; i < messCount; i++ { 54 go fWrite(i) 55 } 56 wg.Wait() 57 58 require.Len(t, w.queue.messagesByOrder, messCount) 59 require.Equal(t, lastSeqNo+messCount, w.queue.lastSeqNo) 60 }) 61 62 t.Run("PredefinedSeqNo", func(t *testing.T) { 63 ctx := xtest.Context(t) 64 65 w := newWriterReconnectorStopped(newWriterReconnectorConfig(WithAutoSetSeqNo(true))) 66 w.firstConnectionHandled.Store(true) 67 require.Error(t, w.Write(ctx, newTestMessages(1))) 68 }) 69 } 70 71 func TestWriterImpl_CheckMessages(t *testing.T) { 72 t.Run("MessageSize", func(t *testing.T) { 73 ctx := xtest.Context(t) 74 w := newWriterReconnectorStopped(newWriterReconnectorConfig()) 75 w.firstConnectionHandled.Store(true) 76 77 maxSize := 5 78 w.cfg.MaxMessageSize = maxSize 79 80 err := w.Write(ctx, []PublicMessage{{Data: bytes.NewReader(make([]byte, maxSize))}}) 81 require.NoError(t, err) 82 83 err = w.Write(ctx, []PublicMessage{{Data: bytes.NewReader(make([]byte, maxSize+1))}}) 84 require.Error(t, err) 85 }) 86 } 87 88 func TestWriterImpl_Write(t *testing.T) { 89 t.Run("PushToQueue", func(t *testing.T) { 90 ctx := context.Background() 91 w := newTestWriterStopped() 92 w.cfg.AutoSetCreatedTime = false 93 w.firstConnectionHandled.Store(true) 94 95 err := w.Write(ctx, newTestMessages(1, 3, 5)) 96 require.NoError(t, err) 97 98 expectedMap := map[int]messageWithDataContent{ 99 1: newTestMessageWithDataContent(1), 100 2: newTestMessageWithDataContent(3), 101 3: newTestMessageWithDataContent(5), 102 } 103 104 for k := range expectedMap { 105 mess := expectedMap[k] 106 _, err = mess.GetEncodedBytes(rawtopiccommon.CodecRaw) 107 require.NoError(t, err) 108 mess.metadataCached = true 109 expectedMap[k] = mess 110 } 111 112 require.Equal(t, expectedMap, w.queue.messagesByOrder) 113 }) 114 t.Run("WriteWithSyncMode", func(t *testing.T) { 115 xtest.TestManyTimes(t, func(t testing.TB) { 116 e := newTestEnv(t, &testEnvOptions{ 117 writerOptions: []PublicWriterOption{ 118 WithWaitAckOnWrite(true), 119 }, 120 }) 121 122 messageTime := time.Date(2022, 9, 7, 11, 34, 0, 0, time.UTC) 123 messageData := []byte("123") 124 125 const seqNo = 31 126 127 writeMessageReceived := make(empty.Chan) 128 e.stream.EXPECT().Send(&rawtopicwriter.WriteRequest{ 129 Messages: []rawtopicwriter.MessageData{ 130 { 131 SeqNo: seqNo, 132 CreatedAt: messageTime, 133 UncompressedSize: int64(len(messageData)), 134 Partitioning: rawtopicwriter.Partitioning{}, 135 Data: messageData, 136 }, 137 }, 138 Codec: rawtopiccommon.CodecRaw, 139 }).Do(func(_ interface{}) { 140 close(writeMessageReceived) 141 }).Return(nil) 142 143 writeCompleted := make(empty.Chan) 144 go func() { 145 err := e.writer.Write(e.ctx, []PublicMessage{{ 146 SeqNo: seqNo, 147 CreatedAt: messageTime, 148 Data: bytes.NewReader(messageData), 149 }}) 150 require.NoError(t, err) 151 close(writeCompleted) 152 }() 153 154 <-writeMessageReceived 155 156 select { 157 case <-writeCompleted: 158 t.Fatal("sync write must complete after receive ack only") 159 default: 160 // pass 161 } 162 163 e.sendFromServer(&rawtopicwriter.WriteResult{ 164 Acks: []rawtopicwriter.WriteAck{ 165 { 166 SeqNo: seqNo, 167 MessageWriteStatus: rawtopicwriter.MessageWriteStatus{ 168 Type: rawtopicwriter.WriteStatusTypeWritten, 169 WrittenOffset: 4, 170 }, 171 }, 172 }, 173 PartitionID: e.partitionID, 174 }) 175 176 xtest.WaitChannelClosed(t, writeCompleted) 177 }) 178 }) 179 } 180 181 func TestWriterImpl_WriteCodecs(t *testing.T) { 182 t.Run("ForceRaw", func(t *testing.T) { 183 var err error 184 e := newTestEnv(t, &testEnvOptions{writerOptions: []PublicWriterOption{WithCodec(rawtopiccommon.CodecRaw)}}) 185 186 messContent := []byte("123") 187 188 messReceived := make(chan rawtopiccommon.Codec, 2) 189 e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { 190 writeReq := message.(*rawtopicwriter.WriteRequest) 191 messReceived <- writeReq.Codec 192 }) 193 194 require.NoError(t, err) 195 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 196 Data: bytes.NewReader(messContent), 197 }})) 198 199 require.Equal(t, rawtopiccommon.CodecRaw, <-messReceived) 200 }) 201 t.Run("ForceGzip", func(t *testing.T) { 202 var err error 203 e := newTestEnv(t, &testEnvOptions{ 204 writerOptions: []PublicWriterOption{WithCodec(rawtopiccommon.CodecGzip)}, 205 topicCodecs: rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecGzip}, 206 }) 207 208 messContent := []byte("123") 209 210 gzipped := &bytes.Buffer{} 211 writer := gzip.NewWriter(gzipped) 212 _, err = writer.Write(messContent) 213 require.NoError(t, err) 214 require.NoError(t, writer.Close()) 215 216 messReceived := make(chan rawtopiccommon.Codec, 2) 217 e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { 218 writeReq := message.(*rawtopicwriter.WriteRequest) 219 messReceived <- writeReq.Codec 220 }) 221 222 require.NoError(t, err) 223 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 224 Data: bytes.NewReader(messContent), 225 }})) 226 227 require.Equal(t, rawtopiccommon.CodecGzip, <-messReceived) 228 }) 229 t.Run("Auto", func(t *testing.T) { 230 e := newTestEnv(t, &testEnvOptions{ 231 writerOptions: []PublicWriterOption{ 232 WithAutoSetSeqNo(true), 233 WithAutoCodec(), 234 }, 235 topicCodecs: rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw, rawtopiccommon.CodecGzip}, 236 }) 237 238 messContentShort := []byte("1") 239 messContentLong := make([]byte, 100000) 240 241 messReceived := make(chan rawtopiccommon.Codec, 2) 242 e.stream.EXPECT().Send(gomock.Any()).Do(func(message rawtopicwriter.ClientMessage) { 243 writeReq := message.(*rawtopicwriter.WriteRequest) 244 messReceived <- writeReq.Codec 245 }).Times(codecMeasureIntervalBatches * 2) 246 247 codecs := make(map[rawtopiccommon.Codec]empty.Struct) 248 249 for i := 0; i < codecMeasureIntervalBatches; i++ { 250 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 251 Data: bytes.NewReader(messContentShort), 252 }})) 253 // wait send 254 codec := <-messReceived 255 codecs[codec] = empty.Struct{} 256 } 257 258 for i := 0; i < codecMeasureIntervalBatches; i++ { 259 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 260 Data: bytes.NewReader(messContentLong), 261 }})) 262 // wait send 263 codec := <-messReceived 264 codecs[codec] = empty.Struct{} 265 } 266 267 // used two different codecs 268 require.Len(t, codecs, 2) 269 }) 270 } 271 272 func TestWriterReconnector_Write_QueueLimit(t *testing.T) { 273 xtest.TestManyTimes(t, func(t testing.TB) { 274 ctx := xtest.Context(t) 275 w := newWriterReconnectorStopped(newWriterReconnectorConfig( 276 WithAutoSetSeqNo(false), 277 WithMaxQueueLen(2), 278 )) 279 w.firstConnectionHandled.Store(true) 280 281 waitStartQueueWait := func(targetWaiters int) { 282 xtest.SpinWaitCondition(t, nil, func() bool { 283 res := getWaitersCount(w.semaphore) == targetWaiters 284 285 return res 286 }) 287 } 288 289 err := w.Write(ctx, newTestMessages(1, 2)) 290 require.NoError(t, err) 291 292 ctxNoQueueSpace, ctxNoQueueSpaceCancel := xcontext.WithCancel(ctx) 293 294 go func() { 295 waitStartQueueWait(1) 296 ctxNoQueueSpaceCancel() 297 }() 298 err = w.Write(ctxNoQueueSpace, newTestMessages(3)) 299 if !errors.Is(err, PublicErrQueueIsFull) { 300 require.ErrorIs(t, err, PublicErrQueueIsFull) 301 } 302 303 go func() { 304 waitStartQueueWait(1) 305 ackErr := w.queue.AcksReceived([]rawtopicwriter.WriteAck{ 306 { 307 SeqNo: 1, 308 }, 309 }) 310 require.NoError(t, ackErr) 311 }() 312 313 err = w.Write(ctx, newTestMessages(3)) 314 require.NoError(t, err) 315 }) 316 } 317 318 func TestEnv(t *testing.T) { 319 xtest.TestManyTimes(t, func(t testing.TB) { 320 env := newTestEnv(t, nil) 321 xtest.WaitChannelClosed(t, env.writer.firstInitResponseProcessedChan) 322 }) 323 } 324 325 func TestWriterImpl_InitSession(t *testing.T) { 326 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 327 lastSeqNo := int64(123) 328 sessionID := "test-session-id" 329 330 w.onWriterChange(&SingleStreamWriter{ 331 ReceivedLastSeqNum: lastSeqNo, 332 LastSeqNumRequested: true, 333 SessionID: sessionID, 334 }) 335 336 require.Equal(t, sessionID, w.sessionID) 337 require.Equal(t, lastSeqNo, w.lastSeqNo) 338 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 339 } 340 341 func TestWriterImpl_WaitInit(t *testing.T) { 342 t.Run("OK", func(t *testing.T) { 343 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 344 expectedInitData := InitialInfo{ 345 LastSeqNum: int64(123), 346 } 347 w.onWriterChange(&SingleStreamWriter{ 348 ReceivedLastSeqNum: expectedInitData.LastSeqNum, 349 LastSeqNumRequested: true, 350 }) 351 352 initData, err := w.WaitInit(context.Background()) 353 require.NoError(t, err) 354 require.Equal(t, expectedInitData, initData) 355 356 err = w.Write(context.Background(), newTestMessages(0)) 357 require.NoError(t, err) 358 359 // one more run is needed to check idempotency 360 anotherInitData, err := w.WaitInit(context.Background()) 361 require.NoError(t, err) 362 require.Equal(t, initData, anotherInitData) 363 364 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 365 }) 366 367 t.Run("contextDeadlineErrorInProgress", func(t *testing.T) { 368 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 369 ctx, cancel := context.WithCancel(context.Background()) 370 371 go func() { 372 // wait until w.WaitInit starts 373 time.Sleep(time.Millisecond) 374 cancel() 375 }() 376 377 _, err := w.WaitInit(ctx) 378 require.ErrorIs(t, err, ctx.Err()) 379 }) 380 381 t.Run("contextDeadlineErrorBeforeStart", func(t *testing.T) { 382 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 383 ctx, cancel := context.WithCancel(context.Background()) 384 cancel() 385 _, err := w.WaitInit(ctx) 386 require.ErrorIs(t, err, ctx.Err()) 387 388 w.onWriterChange(&SingleStreamWriter{}) 389 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 390 }) 391 } 392 393 func TestWriterImpl_Reconnect(t *testing.T) { 394 t.Run("StopReconnectOnUnretryableError", func(t *testing.T) { 395 mc := gomock.NewController(t) 396 strm := NewMockRawTopicWriterStream(mc) 397 398 w := newTestWriterStopped() 399 400 ctx := xtest.Context(t) 401 testErr := errors.New("test") 402 403 connectCalled := false 404 connectCalledChan := make(empty.Chan) 405 406 w.cfg.Connect = func(streamCtxArg context.Context) (RawTopicWriterStream, error) { 407 close(connectCalledChan) 408 connectCalled = true 409 require.NotEqual(t, ctx, streamCtxArg) 410 411 return strm, nil 412 } 413 414 initRequest := testCreateInitRequest(w) 415 strm.EXPECT().Send(&initRequest) 416 strm.EXPECT().Recv().Return(nil, testErr) 417 strm.EXPECT().CloseSend() 418 419 w.connectionLoop(ctx) 420 421 require.True(t, connectCalled) 422 require.ErrorIs(t, w.background.CloseReason(), testErr) 423 }) 424 425 xtest.TestManyTimesWithName(t, "ReconnectOnErrors", func(t testing.TB) { 426 ctx := xtest.Context(t) 427 428 w := newTestWriterStopped() 429 430 mc := gomock.NewController(t) 431 432 type connectionAttemptContext struct { 433 name string 434 stream RawTopicWriterStream 435 connectionError error 436 } 437 438 isFirstConnection := true 439 newStream := func(name string) *MockRawTopicWriterStream { 440 strm := NewMockRawTopicWriterStream(mc) 441 initReq := testCreateInitRequest(w) 442 if isFirstConnection { 443 isFirstConnection = false 444 } else { 445 initReq.GetLastSeqNo = false 446 } 447 448 streamClosed := make(empty.Chan) 449 strm.EXPECT().CloseSend().Do(func() { 450 t.Logf("closed stream: %v", name) 451 close(streamClosed) 452 }) 453 454 strm.EXPECT().Send(&initReq).Do(func(_ interface{}) { 455 t.Logf("sent init request stream: %v", name) 456 }) 457 458 strm.EXPECT().Recv().Do(func() { 459 t.Logf("receive init response stream: %v", name) 460 }).Return(&rawtopicwriter.InitResult{ 461 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{Status: rawydb.StatusSuccess}, 462 SessionID: name, 463 }, nil) 464 465 strm.EXPECT().Recv().Do(func() { 466 t.Logf("waiting close channel: %v", name) 467 xtest.WaitChannelClosed(t, streamClosed) 468 t.Logf("channel closed: %v", name) 469 }).Return(nil, errors.New("test stream closed")).MaxTimes(1) 470 471 return strm 472 } 473 474 strm2 := newStream("strm2") 475 strm2.EXPECT().Send(&rawtopicwriter.WriteRequest{ 476 Messages: []rawtopicwriter.MessageData{ 477 {SeqNo: 1}, 478 }, 479 Codec: rawtopiccommon.CodecRaw, 480 }).Do(func(_ *rawtopicwriter.WriteRequest) { 481 t.Logf("strm2 sent message and return retriable error") 482 }).Return(xerrors.Retryable(errors.New("retriable on strm2"))) 483 484 strm3 := newStream("strm3") 485 strm3.EXPECT().Send(&rawtopicwriter.WriteRequest{ 486 Messages: []rawtopicwriter.MessageData{ 487 {SeqNo: 1}, 488 }, 489 Codec: rawtopiccommon.CodecRaw, 490 }).Do(func(_ *rawtopicwriter.WriteRequest) { 491 t.Logf("strm3 sent message and return unretriable error") 492 }).Return(errors.New("strm3")) 493 494 connectsResult := []connectionAttemptContext{ 495 { 496 name: "step-1 connection error", 497 stream: nil, 498 connectionError: xerrors.Retryable(errors.New("test-1")), 499 }, 500 { 501 name: "step-2 connect and return retryable error on write", 502 stream: strm2, 503 }, 504 { 505 name: "step-3 connect and return unretriable error on write", 506 stream: strm3, 507 }, 508 } 509 510 var connectionAttempt atomic.Int64 511 w.cfg.Connect = func(ctx context.Context) (RawTopicWriterStream, error) { 512 attemptIndex := int(connectionAttempt.Add(1)) - 1 513 t.Logf("connect with attempt index: %v", attemptIndex) 514 res := connectsResult[attemptIndex] 515 516 return res.stream, res.connectionError 517 } 518 519 connectionLoopStopped := make(empty.Chan) 520 go func() { 521 defer close(connectionLoopStopped) 522 w.connectionLoop(ctx) 523 t.Log("connection loop stopped") 524 }() 525 526 err := w.Write(ctx, newTestMessages(1)) 527 require.NoError(t, err) 528 529 xtest.WaitChannelClosedWithTimeout(t, connectionLoopStopped, 4*time.Second) 530 }) 531 } 532 533 func TestAllMessagesHasSameBufCodec(t *testing.T) { 534 t.Run("Empty", func(t *testing.T) { 535 require.True(t, allMessagesHasSameBufCodec(nil)) 536 }) 537 538 t.Run("One", func(t *testing.T) { 539 require.True(t, allMessagesHasSameBufCodec(newTestMessagesWithContent(1))) 540 }) 541 542 t.Run("SameCodecs", func(t *testing.T) { 543 require.True(t, allMessagesHasSameBufCodec(newTestMessagesWithContent(1, 2, 3))) 544 }) 545 t.Run("DifferCodecs", func(t *testing.T) { 546 for i := 0; i < 3; i++ { 547 messages := newTestMessagesWithContent(1, 2, 3) 548 messages[i].bufCodec = rawtopiccommon.CodecGzip 549 require.False(t, allMessagesHasSameBufCodec(messages)) 550 } 551 }) 552 } 553 554 func TestCreateRawMessageData(t *testing.T) { 555 t.Run("Empty", func(t *testing.T) { 556 req, err := createWriteRequest(newTestMessagesWithContent(), rawtopiccommon.CodecRaw) 557 require.NoError(t, err) 558 require.Equal(t, 559 rawtopicwriter.WriteRequest{ 560 Messages: []rawtopicwriter.MessageData{}, 561 Codec: rawtopiccommon.CodecRaw, 562 }, 563 req, 564 ) 565 }) 566 t.Run("WithMessageMetadata", func(t *testing.T) { 567 messages := newTestMessagesWithContent(1) 568 messages[0].Metadata = map[string][]byte{ 569 "a": {1, 2, 3}, 570 "b": {4, 5}, 571 } 572 req, err := createWriteRequest(messages, rawtopiccommon.CodecRaw) 573 574 sort.Slice(req.Messages[0].MetadataItems, func(i, j int) bool { 575 return req.Messages[0].MetadataItems[i].Key < req.Messages[0].MetadataItems[j].Key 576 }) 577 578 require.NoError(t, err) 579 require.Equal(t, rawtopicwriter.WriteRequest{ 580 Messages: []rawtopicwriter.MessageData{ 581 { 582 SeqNo: 1, 583 MetadataItems: []rawtopiccommon.MetadataItem{ 584 { 585 Key: "a", 586 Value: []byte{1, 2, 3}, 587 }, 588 { 589 Key: "b", 590 Value: []byte{4, 5}, 591 }, 592 }, 593 }, 594 }, 595 Codec: rawtopiccommon.CodecRaw, 596 }, req) 597 }) 598 t.Run("WithSeqno", func(t *testing.T) { 599 req, err := createWriteRequest(newTestMessagesWithContent(1, 2, 3), rawtopiccommon.CodecRaw) 600 require.NoError(t, err) 601 require.Equal(t, 602 rawtopicwriter.WriteRequest{ 603 Messages: []rawtopicwriter.MessageData{ 604 { 605 SeqNo: 1, 606 }, 607 { 608 SeqNo: 2, 609 }, 610 { 611 SeqNo: 3, 612 }, 613 }, 614 Codec: rawtopiccommon.CodecRaw, 615 }, 616 req, 617 ) 618 }) 619 } 620 621 func TestSplitMessagesByBufCodec(t *testing.T) { 622 tests := [][]rawtopiccommon.Codec{ 623 nil, 624 {}, 625 {rawtopiccommon.CodecRaw}, 626 {rawtopiccommon.CodecRaw, rawtopiccommon.CodecRaw}, 627 {rawtopiccommon.CodecRaw, rawtopiccommon.CodecGzip}, 628 { 629 rawtopiccommon.CodecRaw, 630 rawtopiccommon.CodecGzip, 631 rawtopiccommon.CodecGzip, 632 rawtopiccommon.CodecRaw, 633 rawtopiccommon.CodecGzip, 634 rawtopiccommon.CodecRaw, 635 rawtopiccommon.CodecRaw, 636 }, 637 } 638 639 for _, test := range tests { 640 t.Run(fmt.Sprint(test), func(t *testing.T) { 641 var messages []messageWithDataContent 642 for index, codec := range test { 643 mess := newTestMessageWithDataContent(index) 644 mess.bufCodec = codec 645 messages = append(messages, mess) 646 } 647 648 groups := splitMessagesByBufCodec(messages) 649 expectedNum := int64(-1) 650 for _, group := range groups { 651 require.NotEmpty(t, group) 652 require.True(t, allMessagesHasSameBufCodec(group)) 653 require.Len(t, group, cap(group)) 654 for _, mess := range group { 655 expectedNum++ 656 require.Equal(t, test[int(expectedNum)], mess.bufCodec) 657 mess.SeqNo = expectedNum 658 } 659 } 660 661 require.Equal(t, int(expectedNum), len(test)-1) 662 }) 663 } 664 } 665 666 func TestCalculateAllowedCodecs(t *testing.T) { 667 customCodecSupported := rawtopiccommon.Codec(rawtopiccommon.CodecCustomerFirst) 668 customCodecUnsupported := rawtopiccommon.Codec(rawtopiccommon.CodecCustomerFirst + 1) 669 encoders := NewEncoderMap() 670 encoders.AddEncoder(customCodecSupported, func(writer io.Writer) (io.WriteCloser, error) { 671 return nil, errors.New("test") 672 }) 673 674 table := []struct { 675 name string 676 force rawtopiccommon.Codec 677 serverCodecs rawtopiccommon.SupportedCodecs 678 expectedResult rawtopiccommon.SupportedCodecs 679 }{ 680 { 681 name: "ForceRawWithEmptyServer", 682 force: rawtopiccommon.CodecRaw, 683 serverCodecs: nil, 684 expectedResult: rawtopiccommon.SupportedCodecs{ 685 rawtopiccommon.CodecRaw, 686 }, 687 }, 688 { 689 name: "ForceRawWithAllowedByServer", 690 force: rawtopiccommon.CodecRaw, 691 serverCodecs: rawtopiccommon.SupportedCodecs{ 692 rawtopiccommon.CodecRaw, 693 rawtopiccommon.CodecGzip, 694 }, 695 expectedResult: rawtopiccommon.SupportedCodecs{ 696 rawtopiccommon.CodecRaw, 697 }, 698 }, 699 { 700 name: "ForceCustomWithAllowedByServer", 701 force: customCodecSupported, 702 serverCodecs: rawtopiccommon.SupportedCodecs{ 703 rawtopiccommon.CodecRaw, 704 rawtopiccommon.CodecGzip, 705 customCodecSupported, 706 }, 707 expectedResult: rawtopiccommon.SupportedCodecs{ 708 customCodecSupported, 709 }, 710 }, 711 { 712 name: "ForceRawWithDeniedByServer", 713 force: rawtopiccommon.CodecRaw, 714 serverCodecs: rawtopiccommon.SupportedCodecs{ 715 rawtopiccommon.CodecGzip, 716 }, 717 expectedResult: nil, 718 }, 719 { 720 name: "NotForcedWithEmptyServerList", 721 force: rawtopiccommon.CodecUNSPECIFIED, 722 serverCodecs: nil, 723 expectedResult: rawtopiccommon.SupportedCodecs{ 724 rawtopiccommon.CodecRaw, 725 rawtopiccommon.CodecGzip, 726 }, 727 }, 728 { 729 name: "NotForcedWithServerGzipOnly", 730 force: rawtopiccommon.CodecUNSPECIFIED, 731 serverCodecs: rawtopiccommon.SupportedCodecs{ 732 rawtopiccommon.CodecGzip, 733 }, 734 expectedResult: rawtopiccommon.SupportedCodecs{ 735 rawtopiccommon.CodecGzip, 736 }, 737 }, 738 { 739 name: "NotForcedCustomCodecSupportedAndAllowedByServer", 740 force: rawtopiccommon.CodecUNSPECIFIED, 741 serverCodecs: rawtopiccommon.SupportedCodecs{ 742 rawtopiccommon.CodecGzip, 743 customCodecSupported, 744 customCodecUnsupported, 745 }, 746 expectedResult: rawtopiccommon.SupportedCodecs{ 747 rawtopiccommon.CodecGzip, 748 customCodecSupported, 749 }, 750 }, 751 } 752 753 for _, test := range table { 754 t.Run(test.name, func(t *testing.T) { 755 res := calculateAllowedCodecs(test.force, encoders, test.serverCodecs) 756 require.Equal(t, test.expectedResult, res) 757 }) 758 } 759 } 760 761 func newTestMessageWithDataContent(num int) messageWithDataContent { 762 res := newMessageDataWithContent(PublicMessage{SeqNo: int64(num)}, testCommonEncoders) 763 764 return res 765 } 766 767 func newTestMessages(numbers ...int) []PublicMessage { 768 messages := make([]PublicMessage, len(numbers)) 769 for i, num := range numbers { 770 messages[i].SeqNo = int64(num) 771 } 772 773 return messages 774 } 775 776 func newTestMessagesWithContent(numbers ...int) []messageWithDataContent { 777 messages := make([]messageWithDataContent, 0, len(numbers)) 778 for _, num := range numbers { 779 messages = append(messages, newTestMessageWithDataContent(num)) 780 } 781 782 return messages 783 } 784 785 func newTestWriterStopped(opts ...PublicWriterOption) *WriterReconnector { 786 cfgOptions := append(defaultTestWriterOptions(), opts...) 787 cfg := newWriterReconnectorConfig(cfgOptions...) 788 res := newWriterReconnectorStopped(cfg) 789 790 if cfg.AdditionalEncoders == nil { 791 res.encodersMap = testCommonEncoders 792 } 793 794 return res 795 } 796 797 func defaultTestWriterOptions() []PublicWriterOption { 798 return []PublicWriterOption{ 799 WithProducerID("test-producer-id"), 800 WithTopic("test-topic"), 801 WithSessionMeta(map[string]string{"test-key": "test-val"}), 802 WithPartitioning(NewPartitioningWithMessageGroupID("test-message-group-id")), 803 WithAutoSetSeqNo(false), 804 WithWaitAckOnWrite(false), 805 WithCodec(rawtopiccommon.CodecRaw), 806 WithAutosetCreatedTime(false), 807 } 808 } 809 810 func isClosed(ch <-chan struct{}) bool { 811 select { 812 case _, existVal := <-ch: 813 if existVal { 814 panic("value, when not expected") 815 } 816 817 return true 818 default: 819 return false 820 } 821 } 822 823 type testEnv struct { 824 ctx context.Context 825 stream *MockRawTopicWriterStream 826 writer *WriterReconnector 827 sendFromServerChannel chan sendFromServerResponse 828 stopReadEvents empty.Chan 829 partitionID int64 830 connectCount int64 831 } 832 833 type testEnvOptions struct { 834 writerOptions []PublicWriterOption 835 lastSeqNo int64 836 topicCodecs rawtopiccommon.SupportedCodecs 837 } 838 839 func newTestEnv(t testing.TB, options *testEnvOptions) *testEnv { 840 if options == nil { 841 options = &testEnvOptions{} 842 } 843 844 res := &testEnv{ 845 ctx: xtest.Context(t), 846 stream: NewMockRawTopicWriterStream(gomock.NewController(t)), 847 sendFromServerChannel: make(chan sendFromServerResponse, 1), 848 stopReadEvents: make(empty.Chan), 849 partitionID: 14, 850 } 851 852 writerOptions := append(defaultTestWriterOptions(), WithConnectFunc(func(ctx context.Context) ( 853 RawTopicWriterStream, 854 error, 855 ) { 856 connectNum := atomic.AddInt64(&res.connectCount, 1) 857 if connectNum > 1 { 858 t.Fatalf("test: default env support most one connection") 859 } 860 861 return res.stream, nil 862 })) 863 writerOptions = append(writerOptions, options.writerOptions...) 864 865 res.writer = newWriterReconnectorStopped(newWriterReconnectorConfig(writerOptions...)) 866 867 res.stream.EXPECT().Recv().DoAndReturn(res.receiveMessageHandler).AnyTimes() 868 869 req := testCreateInitRequest(res.writer) 870 871 res.stream.EXPECT().Send(&req).Do(func(_ interface{}) { 872 supportedCodecs := rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw} 873 if options.topicCodecs != nil { 874 supportedCodecs = options.topicCodecs 875 } 876 res.sendFromServer(&rawtopicwriter.InitResult{ 877 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{}, 878 LastSeqNo: options.lastSeqNo, 879 SessionID: "session-" + t.Name(), 880 PartitionID: res.partitionID, 881 SupportedCodecs: supportedCodecs, 882 }) 883 }).Return(nil) 884 885 streamClosed := make(empty.Chan) 886 res.stream.EXPECT().CloseSend().Do(func() { 887 close(streamClosed) 888 }) 889 890 res.writer.start() 891 require.NoError(t, res.writer.waitFirstInitResponse(res.ctx)) 892 893 t.Cleanup(func() { 894 res.writer.close(context.Background(), errors.New("stop writer test environment")) 895 close(res.stopReadEvents) 896 <-streamClosed 897 }) 898 899 return res 900 } 901 902 func (e *testEnv) sendFromServer(msg rawtopicwriter.ServerMessage) { 903 if msg.StatusData().Status == 0 { 904 msg.SetStatus(rawydb.StatusSuccess) 905 } 906 907 e.sendFromServerChannel <- sendFromServerResponse{msg: msg} 908 } 909 910 func (e *testEnv) receiveMessageHandler() (rawtopicwriter.ServerMessage, error) { 911 select { 912 case <-e.stopReadEvents: 913 return nil, fmt.Errorf("test: stop test environment") 914 case res := <-e.sendFromServerChannel: 915 return res.msg, res.err 916 } 917 } 918 919 type sendFromServerResponse struct { 920 msg rawtopicwriter.ServerMessage 921 err error 922 } 923 924 func testCreateInitRequest(w *WriterReconnector) rawtopicwriter.InitRequest { 925 req := newSingleStreamWriterStopped(context.Background(), w.createWriterStreamConfig(nil)).createInitRequest() 926 927 return req 928 }