github.com/rbisecke/kafka-go@v0.4.27/message_test.go (about)

     1  package kafka
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"math/rand"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/rbisecke/kafka-go/compress/gzip"
    15  	"github.com/rbisecke/kafka-go/compress/lz4"
    16  	"github.com/rbisecke/kafka-go/compress/snappy"
    17  	"github.com/rbisecke/kafka-go/compress/zstd"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  // This regression test covers reading messages using offsets that
    22  // are at the beginning and in the middle of compressed and uncompressed
    23  // v1 message sets
    24  func TestV1BatchOffsets(t *testing.T) {
    25  	const highWatermark = 5000
    26  	const topic = "test-topic"
    27  	var (
    28  		msg0 = Message{
    29  			Offset: 0,
    30  			Key:    []byte("msg-0"),
    31  			Value:  []byte("key-0"),
    32  		}
    33  		msg1 = Message{
    34  			Offset: 1,
    35  			Key:    []byte("msg-1"),
    36  			Value:  []byte("key-1"),
    37  		}
    38  		msg2 = Message{
    39  			Offset: 2,
    40  			Key:    []byte("msg-2"),
    41  			Value:  []byte("key-2"),
    42  		}
    43  	)
    44  
    45  	for _, tc := range []struct {
    46  		name     string
    47  		builder  fetchResponseBuilder
    48  		offset   int64
    49  		expected []Message
    50  		debug    bool
    51  	}{
    52  		{
    53  			name:   "num=1 off=0",
    54  			offset: 0,
    55  			builder: fetchResponseBuilder{
    56  				header: fetchResponseHeader{
    57  					highWatermarkOffset: highWatermark,
    58  					lastStableOffset:    highWatermark,
    59  					topic:               topic,
    60  				},
    61  				msgSets: []messageSetBuilder{
    62  					v1MessageSetBuilder{
    63  						msgs: []Message{msg0},
    64  					},
    65  				},
    66  			},
    67  			expected: []Message{msg0},
    68  		},
    69  		{
    70  			name:   "num=1 off=0 compressed",
    71  			offset: 0,
    72  			builder: fetchResponseBuilder{
    73  				header: fetchResponseHeader{
    74  					highWatermarkOffset: highWatermark,
    75  					lastStableOffset:    highWatermark,
    76  					topic:               topic,
    77  				},
    78  				msgSets: []messageSetBuilder{
    79  					v1MessageSetBuilder{
    80  						codec: new(gzip.Codec),
    81  						msgs:  []Message{msg0},
    82  					},
    83  				},
    84  			},
    85  			expected: []Message{msg0},
    86  		},
    87  		{
    88  			name:   "num=1 off=1",
    89  			offset: 1,
    90  			builder: fetchResponseBuilder{
    91  				header: fetchResponseHeader{
    92  					highWatermarkOffset: highWatermark,
    93  					lastStableOffset:    highWatermark,
    94  					topic:               topic,
    95  				},
    96  				msgSets: []messageSetBuilder{
    97  					v1MessageSetBuilder{
    98  						msgs: []Message{msg1},
    99  					},
   100  				},
   101  			},
   102  			expected: []Message{msg1},
   103  		},
   104  		{
   105  			name:   "num=1 off=1 compressed",
   106  			offset: 1,
   107  			builder: fetchResponseBuilder{
   108  				header: fetchResponseHeader{
   109  					highWatermarkOffset: highWatermark,
   110  					lastStableOffset:    highWatermark,
   111  					topic:               topic,
   112  				},
   113  				msgSets: []messageSetBuilder{
   114  					v1MessageSetBuilder{
   115  						codec: new(gzip.Codec),
   116  						msgs:  []Message{msg1},
   117  					},
   118  				},
   119  			},
   120  			expected: []Message{msg1},
   121  		},
   122  		{
   123  			name:   "num=3 off=0",
   124  			offset: 0,
   125  			builder: fetchResponseBuilder{
   126  				header: fetchResponseHeader{
   127  					highWatermarkOffset: highWatermark,
   128  					lastStableOffset:    highWatermark,
   129  					topic:               topic,
   130  				},
   131  				msgSets: []messageSetBuilder{
   132  					v1MessageSetBuilder{
   133  						msgs: []Message{msg0, msg1, msg2},
   134  					},
   135  				},
   136  			},
   137  			expected: []Message{msg0, msg1, msg2},
   138  		},
   139  		{
   140  			name:   "num=3 off=0 compressed",
   141  			offset: 0,
   142  			builder: fetchResponseBuilder{
   143  				header: fetchResponseHeader{
   144  					highWatermarkOffset: highWatermark,
   145  					lastStableOffset:    highWatermark,
   146  					topic:               topic,
   147  				},
   148  				msgSets: []messageSetBuilder{
   149  					v1MessageSetBuilder{
   150  						codec: new(gzip.Codec),
   151  						msgs:  []Message{msg0, msg1, msg2},
   152  					},
   153  				},
   154  			},
   155  			expected: []Message{msg0, msg1, msg2},
   156  		},
   157  		{
   158  			name:   "num=3 off=1",
   159  			offset: 1,
   160  			debug:  true,
   161  			builder: fetchResponseBuilder{
   162  				header: fetchResponseHeader{
   163  					highWatermarkOffset: highWatermark,
   164  					lastStableOffset:    highWatermark,
   165  					topic:               topic,
   166  				},
   167  				msgSets: []messageSetBuilder{
   168  					v1MessageSetBuilder{
   169  						msgs: []Message{msg0, msg1, msg2},
   170  					},
   171  				},
   172  			},
   173  			expected: []Message{msg1, msg2},
   174  		},
   175  		{
   176  			name:   "num=3 off=1 compressed",
   177  			offset: 1,
   178  			debug:  true,
   179  			builder: fetchResponseBuilder{
   180  				header: fetchResponseHeader{
   181  					highWatermarkOffset: highWatermark,
   182  					lastStableOffset:    highWatermark,
   183  					topic:               topic,
   184  				},
   185  				msgSets: []messageSetBuilder{
   186  					v1MessageSetBuilder{
   187  						codec: new(gzip.Codec),
   188  						msgs:  []Message{msg0, msg1, msg2},
   189  					},
   190  				},
   191  			},
   192  			expected: []Message{msg1, msg2},
   193  		},
   194  		{
   195  			name:   "num=3 off=2 compressed",
   196  			offset: 2,
   197  			debug:  true,
   198  			builder: fetchResponseBuilder{
   199  				header: fetchResponseHeader{
   200  					highWatermarkOffset: highWatermark,
   201  					lastStableOffset:    highWatermark,
   202  					topic:               topic,
   203  				},
   204  				msgSets: []messageSetBuilder{
   205  					v1MessageSetBuilder{
   206  						codec: new(gzip.Codec),
   207  						msgs:  []Message{msg0, msg1, msg2},
   208  					},
   209  				},
   210  			},
   211  			expected: []Message{msg2},
   212  		},
   213  	} {
   214  		t.Run(tc.name, func(t *testing.T) {
   215  			bs := tc.builder.bytes()
   216  			r, err := newReaderHelper(t, bs)
   217  			require.NoError(t, err)
   218  			r.offset = tc.offset
   219  			r.debug = tc.debug
   220  			filter := func(msg Message) (res Message) {
   221  				res.Offset = msg.Offset
   222  				res.Key = msg.Key
   223  				res.Value = msg.Value
   224  				return res
   225  			}
   226  			for _, expected := range tc.expected {
   227  				msg := filter(r.readMessage())
   228  				require.EqualValues(t, expected, msg)
   229  			}
   230  			// finally, verify no more bytes remain
   231  			require.EqualValues(t, 0, r.remain)
   232  			_, err = r.readMessageErr()
   233  			require.EqualError(t, err, errShortRead.Error())
   234  		})
   235  	}
   236  }
   237  
   238  func TestMessageSetReader(t *testing.T) {
   239  	const startOffset = 1000
   240  	const highWatermark = 5000
   241  	const topic = "test-topic"
   242  	msgs := make([]Message, 100)
   243  	for i := 0; i < 100; i++ {
   244  		msgs[i] = Message{
   245  			Time:   time.Now(),
   246  			Offset: int64(i + startOffset),
   247  			Key:    []byte(fmt.Sprintf("key-%d", i)),
   248  			Value:  []byte(fmt.Sprintf("val-%d", i)),
   249  			Headers: []Header{
   250  				{
   251  					Key:   fmt.Sprintf("header-key-%d", i),
   252  					Value: []byte(fmt.Sprintf("header-value-%d", i)),
   253  				},
   254  			},
   255  		}
   256  	}
   257  	defaultHeader := fetchResponseHeader{
   258  		highWatermarkOffset: highWatermark,
   259  		lastStableOffset:    highWatermark,
   260  		topic:               topic,
   261  	}
   262  	for _, tc := range []struct {
   263  		name    string
   264  		builder fetchResponseBuilder
   265  		err     error
   266  		debug   bool
   267  	}{
   268  		{
   269  			name: "empty",
   270  			builder: fetchResponseBuilder{
   271  				header: defaultHeader,
   272  			},
   273  			err: errShortRead,
   274  		},
   275  		{
   276  			name: "v0",
   277  			builder: fetchResponseBuilder{
   278  				header: defaultHeader,
   279  				msgSets: []messageSetBuilder{
   280  					v0MessageSetBuilder{
   281  						msgs: []Message{msgs[0]},
   282  					},
   283  				},
   284  			},
   285  		},
   286  		{
   287  			name: "v0 compressed",
   288  			builder: fetchResponseBuilder{
   289  				header: defaultHeader,
   290  				msgSets: []messageSetBuilder{
   291  					v0MessageSetBuilder{
   292  						codec: new(gzip.Codec),
   293  						msgs:  []Message{msgs[0]},
   294  					},
   295  				},
   296  			},
   297  		},
   298  		{
   299  			name: "v1",
   300  			builder: fetchResponseBuilder{
   301  				header: defaultHeader,
   302  				msgSets: []messageSetBuilder{
   303  					v1MessageSetBuilder{
   304  						msgs: []Message{msgs[0]},
   305  					},
   306  				},
   307  			},
   308  		},
   309  		{
   310  			name: "v1 compressed",
   311  			builder: fetchResponseBuilder{
   312  				header: defaultHeader,
   313  				msgSets: []messageSetBuilder{
   314  					v1MessageSetBuilder{
   315  						codec: new(gzip.Codec),
   316  						msgs:  []Message{msgs[0]},
   317  					},
   318  				},
   319  			},
   320  		},
   321  		{
   322  			name: "v2",
   323  			builder: fetchResponseBuilder{
   324  				header: defaultHeader,
   325  				msgSets: []messageSetBuilder{
   326  					v2MessageSetBuilder{
   327  						msgs: []Message{msgs[0]},
   328  					},
   329  				},
   330  			},
   331  		},
   332  		{
   333  			name: "v2 compressed",
   334  			builder: fetchResponseBuilder{
   335  				header: defaultHeader,
   336  				msgSets: []messageSetBuilder{
   337  					v2MessageSetBuilder{
   338  						codec: new(zstd.Codec),
   339  						msgs:  []Message{msgs[0]},
   340  					},
   341  				},
   342  			},
   343  		},
   344  		{
   345  			name: "v2 multiple messages",
   346  			builder: fetchResponseBuilder{
   347  				header: defaultHeader,
   348  				msgSets: []messageSetBuilder{
   349  					v2MessageSetBuilder{
   350  						msgs: []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]},
   351  					},
   352  				},
   353  			},
   354  		},
   355  		{
   356  			name: "v2 multiple messages compressed",
   357  			builder: fetchResponseBuilder{
   358  				header: defaultHeader,
   359  				msgSets: []messageSetBuilder{
   360  					v2MessageSetBuilder{
   361  						codec: new(snappy.Codec),
   362  						msgs:  []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]},
   363  					},
   364  				},
   365  			},
   366  		},
   367  		{
   368  			name: "v2 mix of compressed and uncompressed message sets",
   369  			builder: fetchResponseBuilder{
   370  				header: defaultHeader,
   371  				msgSets: []messageSetBuilder{
   372  					v2MessageSetBuilder{
   373  						codec: new(snappy.Codec),
   374  						msgs:  []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]},
   375  					},
   376  					v2MessageSetBuilder{
   377  						msgs: []Message{msgs[5], msgs[6], msgs[7], msgs[8], msgs[9]},
   378  					},
   379  					v2MessageSetBuilder{
   380  						codec: new(snappy.Codec),
   381  						msgs:  []Message{msgs[10], msgs[11], msgs[12], msgs[13], msgs[14]},
   382  					},
   383  					v2MessageSetBuilder{
   384  						msgs: []Message{msgs[15], msgs[16], msgs[17], msgs[18], msgs[19]},
   385  					},
   386  				},
   387  			},
   388  		},
   389  		{
   390  			name: "v0 v2 v1 v2 v1 v1 v0 v2",
   391  			builder: fetchResponseBuilder{
   392  				header: defaultHeader,
   393  				msgSets: []messageSetBuilder{
   394  					v0MessageSetBuilder{
   395  						msgs: []Message{msgs[0]},
   396  					},
   397  					v2MessageSetBuilder{
   398  						msgs: []Message{msgs[1], msgs[2]},
   399  					},
   400  					v1MessageSetBuilder{
   401  						msgs: []Message{msgs[3]},
   402  					},
   403  					v2MessageSetBuilder{
   404  						msgs: []Message{msgs[4], msgs[5]},
   405  					},
   406  					v1MessageSetBuilder{
   407  						msgs: []Message{msgs[6]},
   408  					},
   409  					v1MessageSetBuilder{
   410  						msgs: []Message{msgs[7]},
   411  					},
   412  					v0MessageSetBuilder{
   413  						msgs: []Message{msgs[8]},
   414  					},
   415  					v2MessageSetBuilder{
   416  						msgs: []Message{msgs[9], msgs[10]},
   417  					},
   418  				},
   419  			},
   420  		},
   421  		{
   422  			name: "v0 v2 v1 v2 v1 v1 v0 v2 mixed compression",
   423  			builder: fetchResponseBuilder{
   424  				header: defaultHeader,
   425  				msgSets: []messageSetBuilder{
   426  					v0MessageSetBuilder{
   427  						codec: new(gzip.Codec),
   428  						msgs:  []Message{msgs[0]},
   429  					},
   430  					v2MessageSetBuilder{
   431  						codec: new(zstd.Codec),
   432  						msgs:  []Message{msgs[1], msgs[2]},
   433  					},
   434  					v1MessageSetBuilder{
   435  						codec: new(snappy.Codec),
   436  						msgs:  []Message{msgs[3]},
   437  					},
   438  					v2MessageSetBuilder{
   439  						codec: new(lz4.Codec),
   440  						msgs:  []Message{msgs[4], msgs[5]},
   441  					},
   442  					v1MessageSetBuilder{
   443  						codec: new(gzip.Codec),
   444  						msgs:  []Message{msgs[6]},
   445  					},
   446  					v1MessageSetBuilder{
   447  						codec: new(zstd.Codec),
   448  						msgs:  []Message{msgs[7]},
   449  					},
   450  					v0MessageSetBuilder{
   451  						codec: new(snappy.Codec),
   452  						msgs:  []Message{msgs[8]},
   453  					},
   454  					v2MessageSetBuilder{
   455  						codec: new(lz4.Codec),
   456  						msgs:  []Message{msgs[9], msgs[10]},
   457  					},
   458  				},
   459  			},
   460  		},
   461  		{
   462  			name: "v0 v2 v1 v2 v1 v1 v0 v2 mixed compression with non-compressed",
   463  			builder: fetchResponseBuilder{
   464  				header: defaultHeader,
   465  				msgSets: []messageSetBuilder{
   466  					v0MessageSetBuilder{
   467  						codec: new(gzip.Codec),
   468  						msgs:  []Message{msgs[0]},
   469  					},
   470  					v2MessageSetBuilder{
   471  						msgs: []Message{msgs[1], msgs[2]},
   472  					},
   473  					v1MessageSetBuilder{
   474  						codec: new(snappy.Codec),
   475  						msgs:  []Message{msgs[3]},
   476  					},
   477  					v2MessageSetBuilder{
   478  						msgs: []Message{msgs[4], msgs[5]},
   479  					},
   480  					v1MessageSetBuilder{
   481  						msgs: []Message{msgs[6]},
   482  					},
   483  					v1MessageSetBuilder{
   484  						codec: new(zstd.Codec),
   485  						msgs:  []Message{msgs[7]},
   486  					},
   487  					v0MessageSetBuilder{
   488  						msgs: []Message{msgs[8]},
   489  					},
   490  					v2MessageSetBuilder{
   491  						codec: new(lz4.Codec),
   492  						msgs:  []Message{msgs[9], msgs[10]},
   493  					},
   494  				},
   495  			},
   496  		},
   497  	} {
   498  		t.Run(tc.name, func(t *testing.T) {
   499  			rh, err := newReaderHelper(t, tc.builder.bytes())
   500  			require.Equal(t, tc.err, err)
   501  			if tc.err != nil {
   502  				return
   503  			}
   504  			rh.debug = tc.debug
   505  			for _, messageSet := range tc.builder.msgSets {
   506  				for _, expected := range messageSet.messages() {
   507  					msg := rh.readMessage()
   508  					require.Equal(t, expected.Offset, msg.Offset)
   509  					require.Equal(t, string(expected.Key), string(msg.Key))
   510  					require.Equal(t, string(expected.Value), string(msg.Value))
   511  					switch messageSet.(type) {
   512  					case v0MessageSetBuilder, v1MessageSetBuilder:
   513  						// v0 and v1 message sets do not have headers
   514  						require.Len(t, msg.Headers, 0)
   515  					case v2MessageSetBuilder:
   516  						// v2 message sets can have headers
   517  						require.EqualValues(t, expected.Headers, msg.Headers)
   518  					default:
   519  						t.Fatalf("unknown builder: %T", messageSet)
   520  					}
   521  					require.Equal(t, expected.Offset, msg.Offset)
   522  				}
   523  			}
   524  			// verify the reader stack is empty
   525  			require.EqualValues(t, 0, rh.remain)
   526  			require.EqualValues(t, 0, rh.count)
   527  			require.EqualValues(t, 0, rh.remaining())
   528  			require.Nil(t, rh.readerStack.parent)
   529  			// any further message is a short read
   530  			_, err = rh.readMessageErr()
   531  			require.EqualError(t, err, errShortRead.Error())
   532  		})
   533  	}
   534  
   535  }
   536  
   537  func TestMessageSetReaderEmpty(t *testing.T) {
   538  	m := messageSetReader{empty: true}
   539  
   540  	noop := func(*bufio.Reader, int, int) (int, error) {
   541  		return 0, nil
   542  	}
   543  
   544  	offset, timestamp, headers, err := m.readMessage(0, noop, noop)
   545  	if offset != 0 {
   546  		t.Errorf("expected offset of 0, get %d", offset)
   547  	}
   548  	if timestamp != 0 {
   549  		t.Errorf("expected timestamp of 0, get %d", timestamp)
   550  	}
   551  	if headers != nil {
   552  		t.Errorf("expected nil headers, got %v", headers)
   553  	}
   554  	if err != RequestTimedOut {
   555  		t.Errorf("expected RequestTimedOut, got %v", err)
   556  	}
   557  
   558  	if m.remaining() != 0 {
   559  		t.Errorf("expected 0 remaining, got %d", m.remaining())
   560  	}
   561  
   562  	if m.discard() != nil {
   563  		t.Errorf("unexpected error from discard(): %v", m.discard())
   564  	}
   565  }
   566  
   567  func TestMessageFixtures(t *testing.T) {
   568  	type fixtureMessage struct {
   569  		key   string
   570  		value string
   571  	}
   572  	var fixtureMessages = map[string]fixtureMessage{
   573  		"a": {key: "alpha", value: `{"count":0,"filler":"aaaaaaaaaa"}`},
   574  		"b": {key: "beta", value: `{"count":0,"filler":"bbbbbbbbbb"}`},
   575  		"c": {key: "gamma", value: `{"count":0,"filler":"cccccccccc"}`},
   576  		"d": {key: "delta", value: `{"count":0,"filler":"dddddddddd"}`},
   577  		"e": {key: "epsilon", value: `{"count":0,"filler":"eeeeeeeeee"}`},
   578  		"f": {key: "zeta", value: `{"count":0,"filler":"ffffffffff"}`},
   579  		"g": {key: "eta", value: `{"count":0,"filler":"gggggggggg"}`},
   580  		"h": {key: "theta", value: `{"count":0,"filler":"hhhhhhhhhh"}`},
   581  	}
   582  
   583  	for _, tc := range []struct {
   584  		name     string
   585  		file     string
   586  		messages []string
   587  	}{
   588  		{
   589  			name:     "v2 followed by v1",
   590  			file:     "fixtures/v2b-v1.hex",
   591  			messages: []string{"a", "b", "a", "b"},
   592  		},
   593  		{
   594  			name:     "v2 compressed followed by v1 compressed",
   595  			file:     "fixtures/v2bc-v1c.hex",
   596  			messages: []string{"a", "b", "a", "b"},
   597  		},
   598  		{
   599  			name:     "v2 compressed followed by v1 uncompressed",
   600  			file:     "fixtures/v2bc-v1.hex",
   601  			messages: []string{"a", "b", "c", "d"},
   602  		},
   603  		{
   604  			name:     "v2 compressed followed by v1 uncompressed then v1 compressed",
   605  			file:     "fixtures/v2bc-v1-v1c.hex",
   606  			messages: []string{"a", "b", "c", "d", "e", "f"},
   607  		},
   608  		{
   609  			name:     "v2 compressed followed by v1 uncompressed then v1 compressed",
   610  			file:     "fixtures/v2bc-v1-v1c.hex",
   611  			messages: []string{"a", "b", "c", "d", "e", "f"},
   612  		},
   613  		{
   614  			name:     "v1 followed by v1",
   615  			file:     "fixtures/v1-v1.hex",
   616  			messages: []string{"a", "b", "c", "d"},
   617  		},
   618  		{
   619  			name:     "v1 compressed followed by v1 compressed",
   620  			file:     "fixtures/v1c-v1c.hex",
   621  			messages: []string{"a", "b", "c", "d"},
   622  		},
   623  		{
   624  			name:     "v1 compressed followed by v1 uncompressed then v1 compressed",
   625  			file:     "fixtures/v1c-v1-v1c.hex",
   626  			messages: []string{"a", "b", "c", "d", "e", "f"},
   627  		},
   628  		{
   629  			name:     "v2 followed by v2",
   630  			file:     "fixtures/v2-v2.hex",
   631  			messages: []string{"a", "b", "c", "d"},
   632  		},
   633  		{
   634  			name:     "v2 compressed followed by v2 compressed",
   635  			file:     "fixtures/v2c-v2c.hex",
   636  			messages: []string{"a", "b", "c", "d"},
   637  		},
   638  		{
   639  			name:     "v2 compressed followed by v2 uncompressed then v2 compressed",
   640  			file:     "fixtures/v2c-v2-v2c.hex",
   641  			messages: []string{"a", "b", "c", "d", "e", "f"},
   642  		},
   643  		{
   644  			name:     "v1 followed by v2 followed by v1 with mixture of compressed and uncompressed",
   645  			file:     "fixtures/v1-v1c-v2-v2c-v2b-v2b-v2b-v2bc-v1b-v1bc.hex",
   646  			messages: []string{"a", "b", "a", "b", "c", "d", "c", "d", "e", "f", "e", "f", "g", "h", "g", "h", "g", "h", "g", "h"},
   647  		},
   648  	} {
   649  		t.Run(tc.name, func(t *testing.T) {
   650  			bs, err := os.ReadFile(tc.file)
   651  			require.NoError(t, err)
   652  			buf := new(bytes.Buffer)
   653  			_, err = io.Copy(buf, hex.NewDecoder(bytes.NewReader(bs)))
   654  			require.NoError(t, err)
   655  
   656  			// discard 4 byte len and 4 byte correlation id
   657  			bs = make([]byte, 8)
   658  			buf.Read(bs)
   659  
   660  			rh, err := newReaderHelper(t, buf.Bytes())
   661  			require.NoError(t, err)
   662  			messageCount := 0
   663  			expectedMessageCount := len(tc.messages)
   664  			for _, expectedMessageId := range tc.messages {
   665  				expectedMessage := fixtureMessages[expectedMessageId]
   666  				msg := rh.readMessage()
   667  				messageCount++
   668  				require.Equal(t, expectedMessage.key, string(msg.Key))
   669  				require.Equal(t, expectedMessage.value, string(msg.Value))
   670  				t.Logf("Message %d key & value are what we expected: %s -> %s\n",
   671  					messageCount, string(msg.Key), string(msg.Value))
   672  			}
   673  			require.Equal(t, expectedMessageCount, messageCount)
   674  		})
   675  	}
   676  }
   677  
   678  func TestMessageSize(t *testing.T) {
   679  	rand.Seed(time.Now().UnixNano())
   680  	for i := 0; i < 20; i++ {
   681  		t.Run("Run", func(t *testing.T) {
   682  			msg := Message{
   683  				Key:   make([]byte, rand.Intn(200)),
   684  				Value: make([]byte, rand.Intn(200)),
   685  				Time:  randate(),
   686  			}
   687  			expSize := msg.message(nil).size()
   688  			gotSize := msg.size()
   689  			if expSize != gotSize {
   690  				t.Errorf("Expected size %d, but got size %d", expSize, gotSize)
   691  			}
   692  		})
   693  	}
   694  
   695  }
   696  
   697  // https://stackoverflow.com/questions/43495745/how-to-generate-random-date-in-go-lang/43497333#43497333
   698  func randate() time.Time {
   699  	min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
   700  	max := time.Date(2070, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
   701  	delta := max - min
   702  
   703  	sec := rand.Int63n(delta) + min
   704  	return time.Unix(sec, 0)
   705  }
   706  
   707  // readerHelper composes a messageSetReader to provide convenience methods to read
   708  // messages.
   709  type readerHelper struct {
   710  	t *testing.T
   711  	*messageSetReader
   712  	offset int64
   713  }
   714  
   715  func newReaderHelper(t *testing.T, bs []byte) (r *readerHelper, err error) {
   716  	bufReader := bufio.NewReader(bytes.NewReader(bs))
   717  	_, _, remain, err := readFetchResponseHeaderV10(bufReader, len(bs))
   718  	require.NoError(t, err)
   719  	var msgs *messageSetReader
   720  	msgs, err = newMessageSetReader(bufReader, remain)
   721  	if err != nil {
   722  		return
   723  	}
   724  	r = &readerHelper{t: t, messageSetReader: msgs}
   725  	require.Truef(t, msgs.remaining() > 0, "remaining should be > 0 but was %d", msgs.remaining())
   726  	return
   727  }
   728  
   729  func (r *readerHelper) readMessageErr() (msg Message, err error) {
   730  	keyFunc := func(r *bufio.Reader, size int, nbytes int) (remain int, err error) {
   731  		msg.Key, remain, err = readNewBytes(r, size, nbytes)
   732  		return
   733  	}
   734  	valueFunc := func(r *bufio.Reader, size int, nbytes int) (remain int, err error) {
   735  		msg.Value, remain, err = readNewBytes(r, size, nbytes)
   736  		return
   737  	}
   738  	var timestamp int64
   739  	var headers []Header
   740  	r.offset, timestamp, headers, err = r.messageSetReader.readMessage(r.offset, keyFunc, valueFunc)
   741  	if err != nil {
   742  		return
   743  	}
   744  	msg.Offset = r.offset
   745  	msg.Time = time.Unix(timestamp/1000, (timestamp%1000)*1000000)
   746  	msg.Headers = headers
   747  	return
   748  }
   749  
   750  func (r *readerHelper) readMessage() (msg Message) {
   751  	var err error
   752  	msg, err = r.readMessageErr()
   753  	require.NoError(r.t, err)
   754  	return
   755  }