github.com/mongey/jocko@v0.0.0-20171219041507-c0a90d8b143b/broker/broker_test.go (about)

     1  package broker
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"os"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/travisjeffery/jocko"
    15  	"github.com/travisjeffery/jocko/mock"
    16  	"github.com/travisjeffery/jocko/protocol"
    17  	"github.com/travisjeffery/jocko/log"
    18  )
    19  
    20  func TestNew(t *testing.T) {
    21  	tests := []struct {
    22  		name      string
    23  		fields    fields
    24  		setFields func(f *fields)
    25  		wantErr   bool
    26  	}{
    27  		{
    28  			name: "broker ok",
    29  		},
    30  		{
    31  			name:    "no logger error",
    32  			wantErr: true,
    33  			setFields: func(f *fields) {
    34  				f.logger = nil
    35  			},
    36  		},
    37  		{
    38  			name:    "no broker addr error",
    39  			wantErr: true,
    40  			setFields: func(f *fields) {
    41  				f.brokerAddr = ""
    42  			},
    43  		},
    44  		{
    45  			name:    "no raft addr error",
    46  			wantErr: true,
    47  			setFields: func(f *fields) {
    48  				f.raft = &mock.Raft{
    49  					AddrFunc: func() string {
    50  						return ""
    51  					},
    52  				}
    53  			},
    54  		},
    55  		{
    56  			name:    "serf bootstrap error",
    57  			wantErr: true,
    58  			setFields: func(f *fields) {
    59  				f.serf = &mock.Serf{
    60  					BootstrapFunc: func(n *jocko.ClusterMember, rCh chan<- *jocko.ClusterMember) error {
    61  						return errors.New("mock serf bootstrap error")
    62  					},
    63  				}
    64  			},
    65  		},
    66  		{
    67  			name:    "raft bootstrap error",
    68  			wantErr: true,
    69  			setFields: func(f *fields) {
    70  				f.raft = &mock.Raft{
    71  					AddrFunc: f.raft.AddrFunc,
    72  					BootstrapFunc: func(s jocko.Serf, sCh <-chan *jocko.ClusterMember, cCh chan<- jocko.RaftCommand) error {
    73  						return errors.New("mock raft bootstrap error")
    74  					},
    75  				}
    76  			},
    77  		},
    78  	}
    79  	for _, tt := range tests {
    80  		os.RemoveAll("/tmp/jocko")
    81  
    82  		t.Run(tt.name, func(t *testing.T) {
    83  			tt.fields = newFields()
    84  			if tt.setFields != nil {
    85  				tt.setFields(&tt.fields)
    86  			}
    87  			want := &Broker{
    88  				logger:       tt.fields.logger,
    89  				id:           tt.fields.id,
    90  				topicMap:     tt.fields.topicMap,
    91  				replicators:  tt.fields.replicators,
    92  				brokerAddr:   tt.fields.brokerAddr,
    93  				logDir:       tt.fields.logDir,
    94  				raftCommands: tt.fields.raftCommands,
    95  				raft:         tt.fields.raft,
    96  				serf:         tt.fields.serf,
    97  				shutdownCh:   tt.fields.shutdownCh,
    98  				shutdown:     tt.fields.shutdown,
    99  			}
   100  
   101  			got, err := New(tt.fields.id, Addr(tt.fields.brokerAddr), Serf(tt.fields.serf), Raft(tt.fields.raft), Logger(tt.fields.logger), RaftCommands(tt.fields.raftCommands), LogDir(tt.fields.logDir))
   102  
   103  			if err != nil && tt.wantErr {
   104  				return
   105  			} else if (err != nil) != tt.wantErr {
   106  				t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
   107  				return
   108  			}
   109  			if !tt.fields.serf.BootstrapCalled() {
   110  				t.Error("expected serf bootstrap invoked; did not")
   111  			}
   112  			if !tt.fields.raft.BootstrapCalled() {
   113  				t.Error("expected raft bootstrap invoked; did not")
   114  			}
   115  			if got != nil && got.shutdownCh == nil {
   116  				t.Errorf("got.shutdownCh is nil")
   117  			} else if got != nil {
   118  				want.shutdownCh = got.shutdownCh
   119  			}
   120  			if !reflect.DeepEqual(got, want) {
   121  				t.Errorf("New() = %v, want %v", got, want)
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func TestBroker_Run(t *testing.T) {
   128  	mustEncode := func(e protocol.Encoder) []byte {
   129  		var b []byte
   130  		var err error
   131  		if b, err = protocol.Encode(e); err != nil {
   132  			panic(err)
   133  		}
   134  		return b
   135  	}
   136  	type args struct {
   137  		ctx        context.Context
   138  		requestCh  chan jocko.Request
   139  		responseCh chan jocko.Response
   140  		requests   []jocko.Request
   141  		responses  []jocko.Response
   142  	}
   143  	tests := []struct {
   144  		name      string
   145  		fields    fields
   146  		setFields func(f *fields)
   147  		args      args
   148  	}{
   149  		{
   150  			name:   "api versions",
   151  			fields: newFields(),
   152  			args: args{
   153  				requestCh:  make(chan jocko.Request, 2),
   154  				responseCh: make(chan jocko.Response, 2),
   155  				requests: []jocko.Request{{
   156  					Header:  &protocol.RequestHeader{CorrelationID: 1},
   157  					Request: &protocol.APIVersionsRequest{},
   158  				}},
   159  				responses: []jocko.Response{{
   160  					Header:   &protocol.RequestHeader{CorrelationID: 1},
   161  					Response: &protocol.Response{CorrelationID: 1, Body: (&Broker{}).handleAPIVersions(nil, nil)},
   162  				}},
   163  			},
   164  		},
   165  		{
   166  			name:   "create topic ok",
   167  			fields: newFields(),
   168  			args: args{
   169  				requestCh:  make(chan jocko.Request, 2),
   170  				responseCh: make(chan jocko.Response, 2),
   171  				requests: []jocko.Request{{
   172  					Header: &protocol.RequestHeader{CorrelationID: 1},
   173  					Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   174  						Topic:             "the-topic",
   175  						NumPartitions:     1,
   176  						ReplicationFactor: 1,
   177  					}}}},
   178  				},
   179  				responses: []jocko.Response{{
   180  					Header: &protocol.RequestHeader{CorrelationID: 1},
   181  					Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   182  						TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   183  					}},
   184  				}},
   185  			},
   186  		},
   187  		{
   188  			name:   "create topic invalid replication factor error",
   189  			fields: newFields(),
   190  			args: args{
   191  				requestCh:  make(chan jocko.Request, 2),
   192  				responseCh: make(chan jocko.Response, 2),
   193  				requests: []jocko.Request{{
   194  					Header: &protocol.RequestHeader{CorrelationID: 1},
   195  					Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   196  						Topic:             "the-topic",
   197  						NumPartitions:     1,
   198  						ReplicationFactor: 2,
   199  					}}}},
   200  				},
   201  				responses: []jocko.Response{{
   202  					Header: &protocol.RequestHeader{CorrelationID: 1},
   203  					Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   204  						TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrInvalidReplicationFactor.Code()}},
   205  					}},
   206  				}},
   207  			},
   208  		},
   209  		{
   210  			name:   "delete topic",
   211  			fields: newFields(),
   212  			args: args{
   213  				requestCh:  make(chan jocko.Request, 2),
   214  				responseCh: make(chan jocko.Response, 2),
   215  				requests: []jocko.Request{{
   216  					Header: &protocol.RequestHeader{CorrelationID: 1},
   217  					Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   218  						Topic:             "the-topic",
   219  						NumPartitions:     1,
   220  						ReplicationFactor: 1,
   221  					}}}}, {
   222  					Header:  &protocol.RequestHeader{CorrelationID: 2},
   223  					Request: &protocol.DeleteTopicsRequest{Topics: []string{"the-topic"}}},
   224  				},
   225  				responses: []jocko.Response{{
   226  					Header: &protocol.RequestHeader{CorrelationID: 1},
   227  					Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   228  						TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   229  					}},
   230  				}, {
   231  					Header: &protocol.RequestHeader{CorrelationID: 2},
   232  					Response: &protocol.Response{CorrelationID: 2, Body: &protocol.DeleteTopicsResponse{
   233  						TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   234  					}}}},
   235  			},
   236  		},
   237  		{
   238  			name:   "offsets",
   239  			fields: newFields(),
   240  			args: args{
   241  				requestCh:  make(chan jocko.Request, 2),
   242  				responseCh: make(chan jocko.Response, 2),
   243  				requests: []jocko.Request{
   244  					{
   245  						Header: &protocol.RequestHeader{CorrelationID: 1},
   246  						Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   247  							Topic:             "the-topic",
   248  							NumPartitions:     1,
   249  							ReplicationFactor: 1,
   250  						}}},
   251  					},
   252  					{
   253  						Header: &protocol.RequestHeader{CorrelationID: 2},
   254  						Request: &protocol.ProduceRequest{TopicData: []*protocol.TopicData{{
   255  							Topic: "the-topic",
   256  							Data: []*protocol.Data{{
   257  								RecordSet: mustEncode(&protocol.MessageSet{Offset: 0, Messages: []*protocol.Message{{Value: []byte("The message.")}}})}}}}},
   258  					},
   259  					{
   260  						Header:  &protocol.RequestHeader{CorrelationID: 3},
   261  						Request: &protocol.OffsetsRequest{ReplicaID: 0, Topics: []*protocol.OffsetsTopic{{Topic: "the-topic", Partitions: []*protocol.OffsetsPartition{{Partition: 0, Timestamp: -1}}}}},
   262  					},
   263  					{
   264  						Header:  &protocol.RequestHeader{CorrelationID: 4},
   265  						Request: &protocol.OffsetsRequest{ReplicaID: 0, Topics: []*protocol.OffsetsTopic{{Topic: "the-topic", Partitions: []*protocol.OffsetsPartition{{Partition: 0, Timestamp: -2}}}}},
   266  					},
   267  				},
   268  				responses: []jocko.Response{
   269  					{
   270  						Header: &protocol.RequestHeader{CorrelationID: 1},
   271  						Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   272  							TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   273  						}},
   274  					},
   275  					{
   276  						Header: &protocol.RequestHeader{CorrelationID: 2},
   277  						Response: &protocol.Response{CorrelationID: 2, Body: &protocol.ProduceResponses{
   278  							Responses: []*protocol.ProduceResponse{{
   279  								Topic:              "the-topic",
   280  								PartitionResponses: []*protocol.ProducePartitionResponse{{Partition: 0, BaseOffset: 0, ErrorCode: protocol.ErrNone.Code()}},
   281  							}},
   282  						}},
   283  					},
   284  					{
   285  						Header: &protocol.RequestHeader{CorrelationID: 3},
   286  						Response: &protocol.Response{CorrelationID: 3, Body: &protocol.OffsetsResponse{
   287  							Responses: []*protocol.OffsetResponse{{
   288  								Topic:              "the-topic",
   289  								PartitionResponses: []*protocol.PartitionResponse{{Partition: 0, Offsets: []int64{1}, ErrorCode: protocol.ErrNone.Code()}},
   290  							}},
   291  						}},
   292  					},
   293  					{
   294  						Header: &protocol.RequestHeader{CorrelationID: 4},
   295  						Response: &protocol.Response{CorrelationID: 4, Body: &protocol.OffsetsResponse{
   296  							Responses: []*protocol.OffsetResponse{{
   297  								Topic:              "the-topic",
   298  								PartitionResponses: []*protocol.PartitionResponse{{Partition: 0, Offsets: []int64{0}, ErrorCode: protocol.ErrNone.Code()}},
   299  							}},
   300  						}},
   301  					},
   302  				},
   303  			},
   304  		},
   305  		{
   306  			name:   "fetch",
   307  			fields: newFields(),
   308  			args: args{
   309  				requestCh:  make(chan jocko.Request, 2),
   310  				responseCh: make(chan jocko.Response, 2),
   311  				requests: []jocko.Request{
   312  					{
   313  						Header: &protocol.RequestHeader{CorrelationID: 1},
   314  						Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   315  							Topic:             "the-topic",
   316  							NumPartitions:     1,
   317  							ReplicationFactor: 1,
   318  						}}},
   319  					},
   320  					{
   321  						Header: &protocol.RequestHeader{CorrelationID: 2},
   322  						Request: &protocol.ProduceRequest{TopicData: []*protocol.TopicData{{
   323  							Topic: "the-topic",
   324  							Data: []*protocol.Data{{
   325  								RecordSet: mustEncode(&protocol.MessageSet{Offset: 0, Messages: []*protocol.Message{{Value: []byte("The message.")}}})}}}}},
   326  					},
   327  					{
   328  						Header:  &protocol.RequestHeader{CorrelationID: 3},
   329  						Request: &protocol.FetchRequest{ReplicaID: 1, MinBytes: 5, Topics: []*protocol.FetchTopic{{Topic: "the-topic", Partitions: []*protocol.FetchPartition{{Partition: 0, FetchOffset: 0, MaxBytes: 100}}}}},
   330  					},
   331  				},
   332  				responses: []jocko.Response{
   333  					{
   334  						Header: &protocol.RequestHeader{CorrelationID: 1},
   335  						Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   336  							TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   337  						}},
   338  					},
   339  					{
   340  						Header: &protocol.RequestHeader{CorrelationID: 2},
   341  						Response: &protocol.Response{CorrelationID: 2, Body: &protocol.ProduceResponses{
   342  							Responses: []*protocol.ProduceResponse{
   343  								{
   344  									Topic:              "the-topic",
   345  									PartitionResponses: []*protocol.ProducePartitionResponse{{Partition: 0, BaseOffset: 0, ErrorCode: protocol.ErrNone.Code()}},
   346  								},
   347  							},
   348  						}},
   349  					},
   350  					{
   351  						Header: &protocol.RequestHeader{CorrelationID: 3},
   352  						Response: &protocol.Response{CorrelationID: 3, Body: &protocol.FetchResponses{
   353  							Responses: []*protocol.FetchResponse{{
   354  								Topic: "the-topic",
   355  								PartitionResponses: []*protocol.FetchPartitionResponse{{
   356  									Partition:     0,
   357  									ErrorCode:     protocol.ErrNone.Code(),
   358  									HighWatermark: 1,
   359  									RecordSet:     mustEncode(&protocol.MessageSet{Offset: 0, Messages: []*protocol.Message{{Value: []byte("The message.")}}}),
   360  								}},
   361  							}}},
   362  						},
   363  					},
   364  				},
   365  			},
   366  		},
   367  		{
   368  			name:   "metadata",
   369  			fields: newFields(),
   370  			args: args{
   371  				requestCh:  make(chan jocko.Request, 2),
   372  				responseCh: make(chan jocko.Response, 2),
   373  				requests: []jocko.Request{
   374  					{
   375  						Header: &protocol.RequestHeader{CorrelationID: 1},
   376  						Request: &protocol.CreateTopicRequests{Requests: []*protocol.CreateTopicRequest{{
   377  							Topic:             "the-topic",
   378  							NumPartitions:     1,
   379  							ReplicationFactor: 1,
   380  						}}},
   381  					},
   382  					{
   383  						Header: &protocol.RequestHeader{CorrelationID: 2},
   384  						Request: &protocol.ProduceRequest{TopicData: []*protocol.TopicData{{
   385  							Topic: "the-topic",
   386  							Data: []*protocol.Data{{
   387  								RecordSet: mustEncode(&protocol.MessageSet{Offset: 0, Messages: []*protocol.Message{{Value: []byte("The message.")}}})}}}}},
   388  					},
   389  					{
   390  						Header:  &protocol.RequestHeader{CorrelationID: 3},
   391  						Request: &protocol.MetadataRequest{Topics: []string{"the-topic", "unknown-topic"}},
   392  					},
   393  				},
   394  				responses: []jocko.Response{
   395  					{
   396  						Header: &protocol.RequestHeader{CorrelationID: 1},
   397  						Response: &protocol.Response{CorrelationID: 1, Body: &protocol.CreateTopicsResponse{
   398  							TopicErrorCodes: []*protocol.TopicErrorCode{{Topic: "the-topic", ErrorCode: protocol.ErrNone.Code()}},
   399  						}},
   400  					},
   401  					{
   402  						Header: &protocol.RequestHeader{CorrelationID: 2},
   403  						Response: &protocol.Response{CorrelationID: 2, Body: &protocol.ProduceResponses{
   404  							Responses: []*protocol.ProduceResponse{
   405  								{
   406  									Topic:              "the-topic",
   407  									PartitionResponses: []*protocol.ProducePartitionResponse{{Partition: 0, BaseOffset: 0, ErrorCode: protocol.ErrNone.Code()}},
   408  								},
   409  							},
   410  						}},
   411  					},
   412  					{
   413  						Header: &protocol.RequestHeader{CorrelationID: 3},
   414  						Response: &protocol.Response{CorrelationID: 3, Body: &protocol.MetadataResponse{
   415  							Brokers: []*protocol.Broker{{NodeID: 1, Host: "localhost", Port: 9092}},
   416  							TopicMetadata: []*protocol.TopicMetadata{
   417  								{Topic: "the-topic", TopicErrorCode: protocol.ErrNone.Code(), PartitionMetadata: []*protocol.PartitionMetadata{{PartitionErrorCode: protocol.ErrNone.Code(), ParititionID: 0, Leader: 1, Replicas: []int32{1}, ISR: []int32{1}}}},
   418  								{Topic: "unknown-topic", TopicErrorCode: protocol.ErrUnknownTopicOrPartition.Code()},
   419  							},
   420  						}},
   421  					},
   422  				},
   423  			},
   424  		},
   425  		{
   426  			name:   "produce topic/partition doesn't exist error",
   427  			fields: newFields(),
   428  			args: args{
   429  				requestCh:  make(chan jocko.Request, 2),
   430  				responseCh: make(chan jocko.Response, 2),
   431  				requests: []jocko.Request{{
   432  					Header: &protocol.RequestHeader{CorrelationID: 2},
   433  					Request: &protocol.ProduceRequest{TopicData: []*protocol.TopicData{{
   434  						Topic: "another-topic",
   435  						Data: []*protocol.Data{{
   436  							RecordSet: mustEncode(&protocol.MessageSet{Offset: 1, Messages: []*protocol.Message{{Value: []byte("The message.")}}})}}}}}},
   437  				},
   438  				responses: []jocko.Response{{
   439  					Header: &protocol.RequestHeader{CorrelationID: 2},
   440  					Response: &protocol.Response{CorrelationID: 2, Body: &protocol.ProduceResponses{
   441  						Responses: []*protocol.ProduceResponse{{
   442  							Topic:              "another-topic",
   443  							PartitionResponses: []*protocol.ProducePartitionResponse{{Partition: 0, ErrorCode: protocol.ErrUnknownTopicOrPartition.Code()}},
   444  						}},
   445  					}}}},
   446  			},
   447  		},
   448  		{
   449  			name:   "leader and isr leader new partition",
   450  			fields: newFields(),
   451  			args: args{
   452  				requestCh:  make(chan jocko.Request, 2),
   453  				responseCh: make(chan jocko.Response, 2),
   454  				requests: []jocko.Request{{
   455  					Header: &protocol.RequestHeader{CorrelationID: 2},
   456  					Request: &protocol.LeaderAndISRRequest{
   457  						PartitionStates: []*protocol.PartitionState{
   458  							{
   459  								Topic:     "the-topic",
   460  								Partition: 1,
   461  								ISR:       []int32{1},
   462  								Replicas:  []int32{1},
   463  								Leader:    1,
   464  								ZKVersion: 1,
   465  							},
   466  						},
   467  					}},
   468  				},
   469  				responses: []jocko.Response{{
   470  					Header: &protocol.RequestHeader{CorrelationID: 2},
   471  					Response: &protocol.Response{CorrelationID: 2, Body: &protocol.LeaderAndISRResponse{
   472  						Partitions: []*protocol.LeaderAndISRPartition{
   473  							{
   474  								ErrorCode: protocol.ErrNone.Code(),
   475  								Partition: 1,
   476  								Topic:     "the-topic",
   477  							},
   478  						},
   479  					}}}},
   480  			},
   481  		},
   482  		{
   483  			name:   "leader and isr leader become leader",
   484  			fields: newFields(),
   485  			setFields: func(f *fields) {
   486  				f.topicMap = map[string][]*jocko.Partition{
   487  					"the-topic": []*jocko.Partition{{
   488  						Topic:                   "the-topic",
   489  						ID:                      1,
   490  						Replicas:                nil,
   491  						ISR:                     nil,
   492  						Leader:                  0,
   493  						PreferredLeader:         0,
   494  						LeaderAndISRVersionInZK: 0,
   495  					}},
   496  				}
   497  			},
   498  			args: args{
   499  				requestCh:  make(chan jocko.Request, 2),
   500  				responseCh: make(chan jocko.Response, 2),
   501  				requests: []jocko.Request{{
   502  					Header: &protocol.RequestHeader{CorrelationID: 2},
   503  					Request: &protocol.LeaderAndISRRequest{
   504  						PartitionStates: []*protocol.PartitionState{
   505  							{
   506  								Topic:     "the-topic",
   507  								Partition: 1,
   508  								ISR:       []int32{1},
   509  								Replicas:  []int32{1},
   510  								Leader:    1,
   511  								ZKVersion: 1,
   512  							},
   513  						},
   514  					}},
   515  				},
   516  				responses: []jocko.Response{{
   517  					Header: &protocol.RequestHeader{CorrelationID: 2},
   518  					Response: &protocol.Response{CorrelationID: 2, Body: &protocol.LeaderAndISRResponse{
   519  						Partitions: []*protocol.LeaderAndISRPartition{
   520  							{
   521  								ErrorCode: protocol.ErrNone.Code(),
   522  								Partition: 1,
   523  								Topic:     "the-topic",
   524  							},
   525  						},
   526  					}}}},
   527  			},
   528  		},
   529  		{
   530  			name:   "leader and isr leader become follower",
   531  			fields: newFields(),
   532  			setFields: func(f *fields) {
   533  				f.topicMap = map[string][]*jocko.Partition{
   534  					"the-topic": []*jocko.Partition{{
   535  						Topic:                   "the-topic",
   536  						ID:                      1,
   537  						Replicas:                nil,
   538  						ISR:                     nil,
   539  						Leader:                  1,
   540  						PreferredLeader:         1,
   541  						LeaderAndISRVersionInZK: 0,
   542  					}},
   543  				}
   544  			},
   545  			args: args{
   546  				requestCh:  make(chan jocko.Request, 2),
   547  				responseCh: make(chan jocko.Response, 2),
   548  				requests: []jocko.Request{{
   549  					Header: &protocol.RequestHeader{CorrelationID: 2},
   550  					Request: &protocol.LeaderAndISRRequest{
   551  						PartitionStates: []*protocol.PartitionState{
   552  							{
   553  								Topic:     "the-topic",
   554  								Partition: 1,
   555  								ISR:       []int32{1},
   556  								Replicas:  []int32{1},
   557  								Leader:    0,
   558  								ZKVersion: 1,
   559  							},
   560  						},
   561  					}},
   562  				},
   563  				responses: []jocko.Response{{
   564  					Header: &protocol.RequestHeader{CorrelationID: 2},
   565  					Response: &protocol.Response{CorrelationID: 2, Body: &protocol.LeaderAndISRResponse{
   566  						Partitions: []*protocol.LeaderAndISRPartition{
   567  							{
   568  								ErrorCode: protocol.ErrNone.Code(),
   569  								Partition: 1,
   570  								Topic:     "the-topic",
   571  							},
   572  						},
   573  					}}}},
   574  			},
   575  		},
   576  	}
   577  	for _, tt := range tests {
   578  		os.RemoveAll("/tmp/jocko")
   579  		t.Run(tt.name, func(t *testing.T) {
   580  			if tt.setFields != nil {
   581  				tt.setFields(&tt.fields)
   582  			}
   583  			b := &Broker{
   584  				logger:       tt.fields.logger,
   585  				id:           tt.fields.id,
   586  				loner:        tt.fields.loner,
   587  				topicMap:     tt.fields.topicMap,
   588  				replicators:  tt.fields.replicators,
   589  				brokerAddr:   tt.fields.brokerAddr,
   590  				logDir:       tt.fields.logDir,
   591  				raft:         tt.fields.raft,
   592  				serf:         tt.fields.serf,
   593  				raftCommands: tt.fields.raftCommands,
   594  				shutdownCh:   tt.fields.shutdownCh,
   595  				shutdown:     tt.fields.shutdown,
   596  			}
   597  			if tt.fields.topicMap != nil {
   598  				for _, ps := range tt.fields.topicMap {
   599  					for _, p := range ps {
   600  						b.startReplica(p)
   601  					}
   602  				}
   603  			}
   604  			ctx, cancel := context.WithCancel(context.Background())
   605  			go b.Run(ctx, tt.args.requestCh, tt.args.responseCh)
   606  
   607  			for i := 0; i < len(tt.args.requests); i++ {
   608  				tt.args.requestCh <- tt.args.requests[i]
   609  				response := <-tt.args.responseCh
   610  
   611  				switch res := response.Response.(*protocol.Response).Body.(type) {
   612  				// handle timestamp explicitly since we don't know what
   613  				// it'll be set to
   614  				case *protocol.ProduceResponses:
   615  					for _, response := range res.Responses {
   616  						for _, pr := range response.PartitionResponses {
   617  							if pr.ErrorCode != protocol.ErrNone.Code() {
   618  								break
   619  							}
   620  							if pr.Timestamp == 0 {
   621  								t.Error("expected timestamp not to be 0")
   622  							}
   623  							pr.Timestamp = 0
   624  						}
   625  					}
   626  				}
   627  
   628  				if !reflect.DeepEqual(response.Response, tt.args.responses[i].Response) {
   629  					t.Errorf("got %s, want: %s", spewstr(response.Response), spewstr(tt.args.responses[i].Response))
   630  				}
   631  
   632  			}
   633  			cancel()
   634  		})
   635  	}
   636  }
   637  
   638  func spewstr(v interface{}) string {
   639  	var buf bytes.Buffer
   640  	spew.Fdump(&buf, v)
   641  	return buf.String()
   642  }
   643  
   644  func TestBroker_Join(t *testing.T) {
   645  	type args struct {
   646  		addrs []string
   647  	}
   648  	err := errors.New("mock serf join error")
   649  	tests := []struct {
   650  		name      string
   651  		fields    fields
   652  		setFields func(f *fields)
   653  		args      args
   654  		want      protocol.Error
   655  	}{
   656  		{
   657  			name:   "ok",
   658  			fields: newFields(),
   659  			args:   args{addrs: []string{"localhost:9082"}},
   660  			want:   protocol.ErrNone,
   661  		},
   662  		{
   663  			name:   "serf errr",
   664  			fields: newFields(),
   665  			setFields: func(f *fields) {
   666  				f.serf.JoinFunc = func(addrs ...string) (int, error) {
   667  					return -1, err
   668  				}
   669  			},
   670  			args: args{addrs: []string{"localhost:9082"}},
   671  			want: protocol.ErrUnknown.WithErr(err),
   672  		},
   673  	}
   674  	for _, tt := range tests {
   675  		t.Run(tt.name, func(t *testing.T) {
   676  			if tt.setFields != nil {
   677  				tt.setFields(&tt.fields)
   678  			}
   679  			b := &Broker{
   680  				logger:      tt.fields.logger,
   681  				id:          tt.fields.id,
   682  				topicMap:    tt.fields.topicMap,
   683  				replicators: tt.fields.replicators,
   684  				brokerAddr:  tt.fields.brokerAddr,
   685  				logDir:      tt.fields.logDir,
   686  				raft:        tt.fields.raft,
   687  				serf:        tt.fields.serf,
   688  				shutdownCh:  tt.fields.shutdownCh,
   689  				shutdown:    tt.fields.shutdown,
   690  			}
   691  			if got := b.Join(tt.args.addrs...); !reflect.DeepEqual(got, tt.want) {
   692  				t.Errorf("Broker.Join() = %v, want %v", got, tt.want)
   693  			}
   694  			if !tt.fields.serf.JoinCalled() {
   695  				t.Error("expected serf join invoked; did not")
   696  			}
   697  		})
   698  	}
   699  }
   700  
   701  func TestBroker_clusterMembers(t *testing.T) {
   702  	type fields struct {
   703  		logger      log.Logger
   704  		id          int32
   705  		topicMap    map[string][]*jocko.Partition
   706  		replicators map[*jocko.Partition]*Replicator
   707  		brokerAddr  string
   708  		logDir      string
   709  		raft        jocko.Raft
   710  		serf        jocko.Serf
   711  		shutdownCh  chan struct{}
   712  		shutdown    bool
   713  	}
   714  	members := []*jocko.ClusterMember{{ID: 1}}
   715  	tests := []struct {
   716  		name   string
   717  		fields fields
   718  		want   []*jocko.ClusterMember
   719  	}{
   720  		{
   721  			name: "found members ok",
   722  			fields: fields{
   723  				serf: &mock.Serf{
   724  					ClusterFunc: func() []*jocko.ClusterMember {
   725  						return members
   726  					},
   727  				},
   728  			},
   729  			want: members,
   730  		},
   731  	}
   732  	for _, tt := range tests {
   733  		t.Run(tt.name, func(t *testing.T) {
   734  			b := &Broker{
   735  				logger:      tt.fields.logger,
   736  				id:          tt.fields.id,
   737  				topicMap:    tt.fields.topicMap,
   738  				replicators: tt.fields.replicators,
   739  				brokerAddr:  tt.fields.brokerAddr,
   740  				logDir:      tt.fields.logDir,
   741  				raft:        tt.fields.raft,
   742  				serf:        tt.fields.serf,
   743  				shutdownCh:  tt.fields.shutdownCh,
   744  				shutdown:    tt.fields.shutdown,
   745  			}
   746  			if got := b.clusterMembers(); !reflect.DeepEqual(got, tt.want) {
   747  				t.Errorf("Broker.clusterMembers() = %v, want %v", got, tt.want)
   748  			}
   749  		})
   750  	}
   751  }
   752  
   753  func TestBroker_isController(t *testing.T) {
   754  	type fields struct {
   755  		logger      log.Logger
   756  		id          int32
   757  		topicMap    map[string][]*jocko.Partition
   758  		replicators map[*jocko.Partition]*Replicator
   759  		brokerAddr  string
   760  		logDir      string
   761  		raft        jocko.Raft
   762  		serf        jocko.Serf
   763  		shutdownCh  chan struct{}
   764  		shutdown    bool
   765  	}
   766  	tests := []struct {
   767  		name   string
   768  		fields fields
   769  		want   bool
   770  	}{
   771  		{
   772  			name: "is leader",
   773  			fields: fields{
   774  				raft: &mock.Raft{
   775  					IsLeaderFunc: func() bool {
   776  						return true
   777  					},
   778  				},
   779  			},
   780  			want: true,
   781  		},
   782  	}
   783  	for _, tt := range tests {
   784  		t.Run(tt.name, func(t *testing.T) {
   785  			b := &Broker{
   786  				logger:      tt.fields.logger,
   787  				id:          tt.fields.id,
   788  				topicMap:    tt.fields.topicMap,
   789  				replicators: tt.fields.replicators,
   790  				brokerAddr:  tt.fields.brokerAddr,
   791  				logDir:      tt.fields.logDir,
   792  				raft:        tt.fields.raft,
   793  				serf:        tt.fields.serf,
   794  				shutdownCh:  tt.fields.shutdownCh,
   795  				shutdown:    tt.fields.shutdown,
   796  			}
   797  			if got := b.isController(); got != tt.want {
   798  				t.Errorf("Broker.isController() = %v, want %v", got, tt.want)
   799  			}
   800  		})
   801  	}
   802  }
   803  
   804  func TestBroker_topicPartitions(t *testing.T) {
   805  	type fields struct {
   806  		logger      log.Logger
   807  		id          int32
   808  		topicMap    map[string][]*jocko.Partition
   809  		replicators map[*jocko.Partition]*Replicator
   810  		brokerAddr  string
   811  		logDir      string
   812  		raft        jocko.Raft
   813  		serf        jocko.Serf
   814  		shutdownCh  chan struct{}
   815  		shutdown    bool
   816  	}
   817  	type args struct {
   818  		topic string
   819  	}
   820  	tests := []struct {
   821  		name      string
   822  		fields    fields
   823  		args      args
   824  		wantFound []*jocko.Partition
   825  		wantErr   protocol.Error
   826  	}{
   827  		{
   828  			name: "partitions found",
   829  			fields: fields{
   830  				topicMap: map[string][]*jocko.Partition{"topic": []*jocko.Partition{{ID: 1}}},
   831  			},
   832  			args:      args{topic: "topic"},
   833  			wantFound: []*jocko.Partition{{ID: 1}},
   834  			wantErr:   protocol.ErrNone,
   835  		},
   836  		{
   837  			name: "partitions not found",
   838  			fields: fields{
   839  				topicMap: map[string][]*jocko.Partition{"topic": []*jocko.Partition{{ID: 1}}},
   840  			},
   841  			args:      args{topic: "not_topic"},
   842  			wantFound: nil,
   843  			wantErr:   protocol.ErrUnknownTopicOrPartition,
   844  		},
   845  	}
   846  	for _, tt := range tests {
   847  		t.Run(tt.name, func(t *testing.T) {
   848  			b := &Broker{
   849  				logger:      tt.fields.logger,
   850  				id:          tt.fields.id,
   851  				topicMap:    tt.fields.topicMap,
   852  				replicators: tt.fields.replicators,
   853  				brokerAddr:  tt.fields.brokerAddr,
   854  				logDir:      tt.fields.logDir,
   855  				raft:        tt.fields.raft,
   856  				serf:        tt.fields.serf,
   857  				shutdownCh:  tt.fields.shutdownCh,
   858  				shutdown:    tt.fields.shutdown,
   859  			}
   860  			gotFound, gotErr := b.topicPartitions(tt.args.topic)
   861  			if !reflect.DeepEqual(gotFound, tt.wantFound) {
   862  				t.Errorf("Broker.topicPartitions() gotFound = %v, want %v", gotFound, tt.wantFound)
   863  			}
   864  			if !reflect.DeepEqual(gotErr, tt.wantErr) {
   865  				t.Errorf("Broker.topicPartitions() gotErr = %v, want %v", gotErr, tt.wantErr)
   866  			}
   867  		})
   868  	}
   869  }
   870  
   871  func TestBroker_topics(t *testing.T) {
   872  	type fields struct {
   873  		logger      log.Logger
   874  		id          int32
   875  		topicMap    map[string][]*jocko.Partition
   876  		replicators map[*jocko.Partition]*Replicator
   877  		brokerAddr  string
   878  		logDir      string
   879  		raft        jocko.Raft
   880  		serf        jocko.Serf
   881  		shutdownCh  chan struct{}
   882  		shutdown    bool
   883  	}
   884  	topicMap := map[string][]*jocko.Partition{
   885  		"topic": []*jocko.Partition{{ID: 1}},
   886  	}
   887  	tests := []struct {
   888  		name   string
   889  		fields fields
   890  		want   map[string][]*jocko.Partition
   891  	}{
   892  		{
   893  			name: "topic map returned",
   894  			fields: fields{
   895  				topicMap: topicMap},
   896  			want: topicMap,
   897  		},
   898  	}
   899  	for _, tt := range tests {
   900  		t.Run(tt.name, func(t *testing.T) {
   901  			b := &Broker{
   902  				logger:      tt.fields.logger,
   903  				id:          tt.fields.id,
   904  				topicMap:    tt.fields.topicMap,
   905  				replicators: tt.fields.replicators,
   906  				brokerAddr:  tt.fields.brokerAddr,
   907  				logDir:      tt.fields.logDir,
   908  				raft:        tt.fields.raft,
   909  				serf:        tt.fields.serf,
   910  				shutdownCh:  tt.fields.shutdownCh,
   911  				shutdown:    tt.fields.shutdown,
   912  			}
   913  			if got := b.topics(); !reflect.DeepEqual(got, tt.want) {
   914  				t.Errorf("Broker.topics() = %v, want %v", got, tt.want)
   915  			}
   916  		})
   917  	}
   918  }
   919  
   920  func TestBroker_partition(t *testing.T) {
   921  	f := newFields()
   922  	f.topicMap = map[string][]*jocko.Partition{
   923  		"the-topic":   []*jocko.Partition{{ID: 1}},
   924  		"empty-topic": []*jocko.Partition{},
   925  	}
   926  	type args struct {
   927  		topic     string
   928  		partition int32
   929  	}
   930  	tests := []struct {
   931  		name    string
   932  		fields  fields
   933  		args    args
   934  		want    *jocko.Partition
   935  		wanterr protocol.Error
   936  	}{
   937  		{
   938  			name:   "found partitions",
   939  			fields: f,
   940  			args: args{
   941  				topic:     "the-topic",
   942  				partition: 1,
   943  			},
   944  			want:    f.topicMap["the-topic"][0],
   945  			wanterr: protocol.ErrNone,
   946  		},
   947  		{
   948  			name:   "no partitions",
   949  			fields: f,
   950  			args: args{
   951  				topic:     "not-the-topic",
   952  				partition: 1,
   953  			},
   954  			want:    nil,
   955  			wanterr: protocol.ErrUnknownTopicOrPartition,
   956  		},
   957  		{
   958  			name:   "empty partitions",
   959  			fields: f,
   960  			args: args{
   961  				topic:     "empty-topic",
   962  				partition: 1,
   963  			},
   964  			want:    nil,
   965  			wanterr: protocol.ErrUnknownTopicOrPartition,
   966  		},
   967  	}
   968  	for _, tt := range tests {
   969  		t.Run(tt.name, func(t *testing.T) {
   970  			b := &Broker{
   971  				logger:      tt.fields.logger,
   972  				id:          tt.fields.id,
   973  				topicMap:    tt.fields.topicMap,
   974  				replicators: tt.fields.replicators,
   975  				brokerAddr:  tt.fields.brokerAddr,
   976  				logDir:      tt.fields.logDir,
   977  				raft:        tt.fields.raft,
   978  				serf:        tt.fields.serf,
   979  				shutdownCh:  tt.fields.shutdownCh,
   980  				shutdown:    tt.fields.shutdown,
   981  			}
   982  			got, goterr := b.partition(tt.args.topic, tt.args.partition)
   983  			if !reflect.DeepEqual(got, tt.want) {
   984  				t.Errorf("Broker.partition() got = %v, want %v", got, tt.want)
   985  			}
   986  			if !reflect.DeepEqual(goterr, tt.wanterr) {
   987  				t.Errorf("Broker.partition() goterr = %v, want %v", goterr, tt.wanterr)
   988  			}
   989  		})
   990  	}
   991  }
   992  
   993  func TestBroker_createPartition(t *testing.T) {
   994  	type fields struct {
   995  		logger      log.Logger
   996  		id          int32
   997  		topicMap    map[string][]*jocko.Partition
   998  		replicators map[*jocko.Partition]*Replicator
   999  		brokerAddr  string
  1000  		logDir      string
  1001  		raft        jocko.Raft
  1002  		serf        jocko.Serf
  1003  		shutdownCh  chan struct{}
  1004  		shutdown    bool
  1005  	}
  1006  	type args struct {
  1007  		partition *jocko.Partition
  1008  	}
  1009  	raft := &mock.Raft{
  1010  		ApplyFunc: func(c jocko.RaftCommand) error {
  1011  			if c.Cmd != createPartition {
  1012  				t.Errorf("Broker.createPartition() c.Cmd = %v, want %v", c.Cmd, createPartition)
  1013  			}
  1014  			return nil
  1015  		},
  1016  	}
  1017  	tests := []struct {
  1018  		name    string
  1019  		fields  fields
  1020  		args    args
  1021  		wantErr bool
  1022  	}{
  1023  		{
  1024  			name: "called apply",
  1025  			fields: fields{
  1026  				raft: raft,
  1027  			},
  1028  			args:    args{partition: &jocko.Partition{ID: 1}},
  1029  			wantErr: false,
  1030  		},
  1031  	}
  1032  	for _, tt := range tests {
  1033  		t.Run(tt.name, func(t *testing.T) {
  1034  			b := &Broker{
  1035  				logger:      tt.fields.logger,
  1036  				id:          tt.fields.id,
  1037  				topicMap:    tt.fields.topicMap,
  1038  				replicators: tt.fields.replicators,
  1039  				brokerAddr:  tt.fields.brokerAddr,
  1040  				logDir:      tt.fields.logDir,
  1041  				raft:        tt.fields.raft,
  1042  				serf:        tt.fields.serf,
  1043  				shutdownCh:  tt.fields.shutdownCh,
  1044  				shutdown:    tt.fields.shutdown,
  1045  			}
  1046  			if err := b.createPartition(tt.args.partition); (err != nil) != tt.wantErr {
  1047  				t.Errorf("Broker.createPartition() error = %v, wantErr %v", err, tt.wantErr)
  1048  			}
  1049  			if !raft.ApplyCalled() {
  1050  				t.Errorf("Broker.createPartition() raft.ApplyCalled() = %v, want %v", raft.ApplyCalled(), true)
  1051  			}
  1052  		})
  1053  	}
  1054  }
  1055  
  1056  func TestBroker_clusterMember(t *testing.T) {
  1057  	type fields struct {
  1058  		logger      log.Logger
  1059  		id          int32
  1060  		topicMap    map[string][]*jocko.Partition
  1061  		replicators map[*jocko.Partition]*Replicator
  1062  		brokerAddr  string
  1063  		logDir      string
  1064  		raft        jocko.Raft
  1065  		serf        jocko.Serf
  1066  		shutdownCh  chan struct{}
  1067  		shutdown    bool
  1068  	}
  1069  	member := &jocko.ClusterMember{ID: 1}
  1070  	serf := &mock.Serf{
  1071  		MemberFunc: func(id int32) *jocko.ClusterMember {
  1072  			if id != member.ID {
  1073  				t.Errorf("serf.Member() id = %v, want %v", id, member.ID)
  1074  			}
  1075  			return member
  1076  		},
  1077  	}
  1078  	type args struct {
  1079  		id int32
  1080  	}
  1081  	tests := []struct {
  1082  		name   string
  1083  		fields fields
  1084  		args   args
  1085  		want   *jocko.ClusterMember
  1086  	}{
  1087  		{
  1088  			name: "found member",
  1089  			fields: fields{
  1090  				serf: serf,
  1091  			},
  1092  			args: args{id: 1},
  1093  			want: member,
  1094  		},
  1095  	}
  1096  	for _, tt := range tests {
  1097  		t.Run(tt.name, func(t *testing.T) {
  1098  			b := &Broker{
  1099  				logger:      tt.fields.logger,
  1100  				id:          tt.fields.id,
  1101  				topicMap:    tt.fields.topicMap,
  1102  				replicators: tt.fields.replicators,
  1103  				brokerAddr:  tt.fields.brokerAddr,
  1104  				logDir:      tt.fields.logDir,
  1105  				raft:        tt.fields.raft,
  1106  				serf:        tt.fields.serf,
  1107  				shutdownCh:  tt.fields.shutdownCh,
  1108  				shutdown:    tt.fields.shutdown,
  1109  			}
  1110  			if got := b.clusterMember(tt.args.id); !reflect.DeepEqual(got, tt.want) {
  1111  				t.Errorf("Broker.clusterMember() = %v, want %v", got, tt.want)
  1112  			}
  1113  			if !serf.MemberCalled() {
  1114  				t.Errorf("serf.MemberCalled() = %v, want %v", serf.MemberCalled(), true)
  1115  			}
  1116  		})
  1117  	}
  1118  }
  1119  
  1120  func TestBroker_startReplica(t *testing.T) {
  1121  	type args struct {
  1122  		partition *jocko.Partition
  1123  	}
  1124  	partition := &jocko.Partition{
  1125  		Topic:  "the-topic",
  1126  		ID:     1,
  1127  		Leader: 1,
  1128  	}
  1129  	tests := []struct {
  1130  		name      string
  1131  		setFields func(f *fields)
  1132  		args      args
  1133  		want      protocol.Error
  1134  	}{
  1135  		{
  1136  			name: "started replica as leader",
  1137  			args: args{
  1138  				partition: partition,
  1139  			},
  1140  			want: protocol.ErrNone,
  1141  		},
  1142  		{
  1143  			name: "started replica as follower",
  1144  			args: args{
  1145  				partition: &jocko.Partition{
  1146  					ID:       1,
  1147  					Topic:    "replica-topic",
  1148  					Replicas: []int32{1},
  1149  					Leader:   2,
  1150  				},
  1151  			},
  1152  			want: protocol.ErrNone,
  1153  		},
  1154  		{
  1155  			name: "started replica with existing topic",
  1156  			setFields: func(f *fields) {
  1157  				f.topicMap["existing-topic"] = []*jocko.Partition{
  1158  					{
  1159  						ID:    1,
  1160  						Topic: "existing-topic",
  1161  					},
  1162  				}
  1163  			},
  1164  			args: args{
  1165  				partition: &jocko.Partition{ID: 2, Topic: "existing-topic"},
  1166  			},
  1167  			want: protocol.ErrNone,
  1168  		},
  1169  		// TODO: Possible bug. If a duplicate partition is added,
  1170  		//   the partition will be appended to the partitions as a duplicate.
  1171  		// {
  1172  		// 	name:   "started replica with dupe partition",
  1173  		// 	fields: f,
  1174  		// 	args: args{
  1175  		// 		partition: &jocko.Partition{ID: 1, Topic: "existing-topic"},
  1176  		// 	},
  1177  		// 	want: protocol.ErrNone,
  1178  		// },
  1179  		{
  1180  			name: "started replica with commitlog error",
  1181  			setFields: func(f *fields) {
  1182  				f.logDir = ""
  1183  			},
  1184  			args: args{
  1185  				partition: &jocko.Partition{Leader: 1},
  1186  			},
  1187  			want: protocol.ErrUnknown.WithErr(errors.New("mkdir failed: mkdir /0: permission denied")),
  1188  		},
  1189  	}
  1190  	for _, tt := range tests {
  1191  		fields := newFields()
  1192  		if tt.setFields != nil {
  1193  			tt.setFields(&fields)
  1194  		}
  1195  		t.Run(tt.name, func(t *testing.T) {
  1196  			b := &Broker{
  1197  				logger:      fields.logger,
  1198  				id:          fields.id,
  1199  				topicMap:    fields.topicMap,
  1200  				replicators: fields.replicators,
  1201  				brokerAddr:  fields.brokerAddr,
  1202  				logDir:      fields.logDir,
  1203  				raft:        fields.raft,
  1204  				serf:        fields.serf,
  1205  				shutdownCh:  fields.shutdownCh,
  1206  				shutdown:    fields.shutdown,
  1207  			}
  1208  			if got := b.startReplica(tt.args.partition); got.Error() != tt.want.Error() {
  1209  				t.Errorf("Broker.startReplica() = %v, want %v", got, tt.want)
  1210  			}
  1211  			got, err := b.partition(tt.args.partition.Topic, tt.args.partition.ID)
  1212  			if !reflect.DeepEqual(got, tt.args.partition) {
  1213  				t.Errorf("Broker.partition() = %v, want %v", got, partition)
  1214  			}
  1215  			parts := map[int32]*jocko.Partition{}
  1216  			for _, p := range b.topicMap[tt.args.partition.Topic] {
  1217  				if _, ok := parts[p.ID]; ok {
  1218  					t.Errorf("Broker.topicPartition contains dupes, dupe %v", p)
  1219  				}
  1220  				parts[p.ID] = p
  1221  			}
  1222  			if err != protocol.ErrNone {
  1223  				t.Errorf("Broker.partition() err = %v, want %v", err, protocol.ErrNone)
  1224  			}
  1225  		})
  1226  	}
  1227  }
  1228  
  1229  func TestBroker_createTopic(t *testing.T) {
  1230  	type fields struct {
  1231  		logger      log.Logger
  1232  		id          int32
  1233  		topicMap    map[string][]*jocko.Partition
  1234  		replicators map[*jocko.Partition]*Replicator
  1235  		brokerAddr  string
  1236  		logDir      string
  1237  		raft        jocko.Raft
  1238  		serf        jocko.Serf
  1239  		shutdownCh  chan struct{}
  1240  		shutdown    bool
  1241  	}
  1242  	type args struct {
  1243  		topic             string
  1244  		partitions        int32
  1245  		replicationFactor int16
  1246  	}
  1247  	tests := []struct {
  1248  		name   string
  1249  		fields fields
  1250  		args   args
  1251  		want   protocol.Error
  1252  	}{
  1253  	// TODO: Add test cases.
  1254  	}
  1255  	for _, tt := range tests {
  1256  		t.Run(tt.name, func(t *testing.T) {
  1257  			b := &Broker{
  1258  				logger:      tt.fields.logger,
  1259  				id:          tt.fields.id,
  1260  				topicMap:    tt.fields.topicMap,
  1261  				replicators: tt.fields.replicators,
  1262  				brokerAddr:  tt.fields.brokerAddr,
  1263  				logDir:      tt.fields.logDir,
  1264  				raft:        tt.fields.raft,
  1265  				serf:        tt.fields.serf,
  1266  				shutdownCh:  tt.fields.shutdownCh,
  1267  				shutdown:    tt.fields.shutdown,
  1268  			}
  1269  			if got := b.createTopic(tt.args.topic, tt.args.partitions, tt.args.replicationFactor); !reflect.DeepEqual(got, tt.want) {
  1270  				t.Errorf("Broker.createTopic() = %v, want %v", got, tt.want)
  1271  			}
  1272  		})
  1273  	}
  1274  }
  1275  
  1276  func TestBroker_deleteTopic(t *testing.T) {
  1277  	type fields struct {
  1278  		logger      log.Logger
  1279  		id          int32
  1280  		topicMap    map[string][]*jocko.Partition
  1281  		replicators map[*jocko.Partition]*Replicator
  1282  		brokerAddr  string
  1283  		logDir      string
  1284  		raft        jocko.Raft
  1285  		serf        jocko.Serf
  1286  		shutdownCh  chan struct{}
  1287  		shutdown    bool
  1288  	}
  1289  	type args struct {
  1290  		topic string
  1291  	}
  1292  	tests := []struct {
  1293  		name   string
  1294  		fields fields
  1295  		args   args
  1296  		want   protocol.Error
  1297  	}{
  1298  	// TODO: Add test cases.
  1299  	}
  1300  	for _, tt := range tests {
  1301  		t.Run(tt.name, func(t *testing.T) {
  1302  			b := &Broker{
  1303  				logger:      tt.fields.logger,
  1304  				id:          tt.fields.id,
  1305  				topicMap:    tt.fields.topicMap,
  1306  				replicators: tt.fields.replicators,
  1307  				brokerAddr:  tt.fields.brokerAddr,
  1308  				logDir:      tt.fields.logDir,
  1309  				raft:        tt.fields.raft,
  1310  				serf:        tt.fields.serf,
  1311  				shutdownCh:  tt.fields.shutdownCh,
  1312  				shutdown:    tt.fields.shutdown,
  1313  			}
  1314  			if got := b.deleteTopic(tt.args.topic); !reflect.DeepEqual(got, tt.want) {
  1315  				t.Errorf("Broker.deleteTopic() = %v, want %v", got, tt.want)
  1316  			}
  1317  		})
  1318  	}
  1319  }
  1320  
  1321  func TestBroker_deletePartitions(t *testing.T) {
  1322  	type fields struct {
  1323  		logger      log.Logger
  1324  		id          int32
  1325  		topicMap    map[string][]*jocko.Partition
  1326  		replicators map[*jocko.Partition]*Replicator
  1327  		brokerAddr  string
  1328  		logDir      string
  1329  		raft        jocko.Raft
  1330  		serf        jocko.Serf
  1331  		shutdownCh  chan struct{}
  1332  		shutdown    bool
  1333  	}
  1334  	type args struct {
  1335  		tp *jocko.Partition
  1336  	}
  1337  	tests := []struct {
  1338  		name    string
  1339  		fields  fields
  1340  		args    args
  1341  		wantErr bool
  1342  	}{
  1343  	// TODO: Add test cases.
  1344  	}
  1345  	for _, tt := range tests {
  1346  		t.Run(tt.name, func(t *testing.T) {
  1347  			b := &Broker{
  1348  				logger:      tt.fields.logger,
  1349  				id:          tt.fields.id,
  1350  				topicMap:    tt.fields.topicMap,
  1351  				replicators: tt.fields.replicators,
  1352  				brokerAddr:  tt.fields.brokerAddr,
  1353  				logDir:      tt.fields.logDir,
  1354  				raft:        tt.fields.raft,
  1355  				serf:        tt.fields.serf,
  1356  				shutdownCh:  tt.fields.shutdownCh,
  1357  				shutdown:    tt.fields.shutdown,
  1358  			}
  1359  			if err := b.deletePartitions(tt.args.tp); (err != nil) != tt.wantErr {
  1360  				t.Errorf("Broker.deletePartitions() error = %v, wantErr %v", err, tt.wantErr)
  1361  			}
  1362  		})
  1363  	}
  1364  }
  1365  
  1366  func TestBroker_Shutdown(t *testing.T) {
  1367  	tests := []struct {
  1368  		name    string
  1369  		fields  fields
  1370  		wantErr bool
  1371  	}{
  1372  		{
  1373  			name:    "shutdown ok",
  1374  			fields:  newFields(),
  1375  			wantErr: false,
  1376  		},
  1377  	}
  1378  	for _, tt := range tests {
  1379  		t.Run(tt.name, func(t *testing.T) {
  1380  			b, err := New(tt.fields.id, Addr(tt.fields.brokerAddr), Serf(tt.fields.serf), Raft(tt.fields.raft), Logger(tt.fields.logger), RaftCommands(tt.fields.raftCommands), LogDir(tt.fields.logDir))
  1381  			if err != nil {
  1382  				t.Errorf("Broker.New() error = %v, wanted nil", err)
  1383  			}
  1384  			if err := b.Shutdown(); (err != nil) != tt.wantErr {
  1385  				t.Errorf("Broker.Shutdown() error = %v, wantErr %v", err, tt.wantErr)
  1386  			}
  1387  			if tt.fields.raft.ShutdownCalled() != true {
  1388  				t.Errorf("did not shutdown raft")
  1389  			}
  1390  			if tt.fields.serf.ShutdownCalled() != true {
  1391  				t.Errorf("did not shutdown raft")
  1392  			}
  1393  		})
  1394  	}
  1395  }
  1396  
  1397  func TestBroker_becomeFollower(t *testing.T) {
  1398  	type fields struct {
  1399  		logger      log.Logger
  1400  		id          int32
  1401  		topicMap    map[string][]*jocko.Partition
  1402  		replicators map[*jocko.Partition]*Replicator
  1403  		brokerAddr  string
  1404  		logDir      string
  1405  		raft        jocko.Raft
  1406  		serf        jocko.Serf
  1407  		shutdownCh  chan struct{}
  1408  		shutdown    bool
  1409  	}
  1410  	type args struct {
  1411  		topic          string
  1412  		partitionID    int32
  1413  		partitionState *protocol.PartitionState
  1414  	}
  1415  	tests := []struct {
  1416  		name   string
  1417  		fields fields
  1418  		args   args
  1419  		want   protocol.Error
  1420  	}{
  1421  	// TODO: Add test cases.
  1422  	}
  1423  	for _, tt := range tests {
  1424  		t.Run(tt.name, func(t *testing.T) {
  1425  			b := &Broker{
  1426  				logger:      tt.fields.logger,
  1427  				id:          tt.fields.id,
  1428  				topicMap:    tt.fields.topicMap,
  1429  				replicators: tt.fields.replicators,
  1430  				brokerAddr:  tt.fields.brokerAddr,
  1431  				logDir:      tt.fields.logDir,
  1432  				raft:        tt.fields.raft,
  1433  				serf:        tt.fields.serf,
  1434  				shutdownCh:  tt.fields.shutdownCh,
  1435  				shutdown:    tt.fields.shutdown,
  1436  			}
  1437  			if got := b.becomeFollower(tt.args.topic, tt.args.partitionID, tt.args.partitionState); !reflect.DeepEqual(got, tt.want) {
  1438  				t.Errorf("Broker.becomeFollower() = %v, want %v", got, tt.want)
  1439  			}
  1440  		})
  1441  	}
  1442  }
  1443  
  1444  func TestBroker_becomeLeader(t *testing.T) {
  1445  	type fields struct {
  1446  		logger      log.Logger
  1447  		id          int32
  1448  		topicMap    map[string][]*jocko.Partition
  1449  		replicators map[*jocko.Partition]*Replicator
  1450  		brokerAddr  string
  1451  		logDir      string
  1452  		raft        jocko.Raft
  1453  		serf        jocko.Serf
  1454  		shutdownCh  chan struct{}
  1455  		shutdown    bool
  1456  	}
  1457  	type args struct {
  1458  		topic          string
  1459  		partitionID    int32
  1460  		partitionState *protocol.PartitionState
  1461  	}
  1462  	tests := []struct {
  1463  		name   string
  1464  		fields fields
  1465  		args   args
  1466  		want   protocol.Error
  1467  	}{
  1468  	// TODO: Add test cases.
  1469  	}
  1470  	for _, tt := range tests {
  1471  		t.Run(tt.name, func(t *testing.T) {
  1472  			b := &Broker{
  1473  				logger:      tt.fields.logger,
  1474  				id:          tt.fields.id,
  1475  				topicMap:    tt.fields.topicMap,
  1476  				replicators: tt.fields.replicators,
  1477  				brokerAddr:  tt.fields.brokerAddr,
  1478  				logDir:      tt.fields.logDir,
  1479  				raft:        tt.fields.raft,
  1480  				serf:        tt.fields.serf,
  1481  				shutdownCh:  tt.fields.shutdownCh,
  1482  				shutdown:    tt.fields.shutdown,
  1483  			}
  1484  			if got := b.becomeLeader(tt.args.topic, tt.args.partitionID, tt.args.partitionState); !reflect.DeepEqual(got, tt.want) {
  1485  				t.Errorf("Broker.becomeLeader() = %v, want %v", got, tt.want)
  1486  			}
  1487  		})
  1488  	}
  1489  }
  1490  
  1491  func Test_contains(t *testing.T) {
  1492  	type args struct {
  1493  		rs []int32
  1494  		r  int32
  1495  	}
  1496  	tests := []struct {
  1497  		name string
  1498  		args args
  1499  		want bool
  1500  	}{
  1501  	// TODO: Add test cases.
  1502  	}
  1503  	for _, tt := range tests {
  1504  		t.Run(tt.name, func(t *testing.T) {
  1505  			if got := contains(tt.args.rs, tt.args.r); got != tt.want {
  1506  				t.Errorf("contains() = %v, want %v", got, tt.want)
  1507  			}
  1508  		})
  1509  	}
  1510  }
  1511  
  1512  type fields struct {
  1513  	id           int32
  1514  	serf         *mock.Serf
  1515  	raft         *mock.Raft
  1516  	raftCommands chan jocko.RaftCommand
  1517  	logger       log.Logger
  1518  	topicMap     map[string][]*jocko.Partition
  1519  	replicators  map[*jocko.Partition]*Replicator
  1520  	brokerAddr   string
  1521  	loner        bool
  1522  	logDir       string
  1523  	shutdownCh   chan struct{}
  1524  	shutdown     bool
  1525  }
  1526  
  1527  func newFields() fields {
  1528  	serf := &mock.Serf{
  1529  		BootstrapFunc: func(n *jocko.ClusterMember, rCh chan<- *jocko.ClusterMember) error {
  1530  			if n == nil {
  1531  				return errors.New("*jocko.ClusterMember is nil")
  1532  			}
  1533  			if rCh == nil {
  1534  				return errors.New("chan<- *jocko.ClusterMember is nil")
  1535  			}
  1536  			return nil
  1537  		},
  1538  		JoinFunc: func(addrs ...string) (int, error) {
  1539  			return 1, nil
  1540  		},
  1541  		ClusterFunc: func() []*jocko.ClusterMember {
  1542  			return []*jocko.ClusterMember{{ID: 1, Port: 9092, IP: "localhost"}}
  1543  		},
  1544  		MemberFunc: func(memberID int32) *jocko.ClusterMember {
  1545  			return &jocko.ClusterMember{ID: 1}
  1546  		},
  1547  		ShutdownFunc: func() error {
  1548  			return nil
  1549  		},
  1550  	}
  1551  	raft := &mock.Raft{
  1552  		AddrFunc: func() string {
  1553  			return "localhost:9093"
  1554  		},
  1555  		BootstrapFunc: func(s jocko.Serf, sCh <-chan *jocko.ClusterMember, cCh chan<- jocko.RaftCommand) error {
  1556  			if s == nil {
  1557  				return errors.New("jocko.Serf is nil")
  1558  			}
  1559  			if sCh == nil {
  1560  				return errors.New("<-chan *jocko.ClusterMember is nil")
  1561  			}
  1562  			if cCh == nil {
  1563  				return errors.New("chan<- jocko.RaftCommand is nil")
  1564  			}
  1565  			return nil
  1566  		},
  1567  		IsLeaderFunc: func() bool {
  1568  			return true
  1569  		},
  1570  		ApplyFunc: func(jocko.RaftCommand) error {
  1571  			return nil
  1572  		},
  1573  		ShutdownFunc: func() error {
  1574  			return nil
  1575  		},
  1576  	}
  1577  	return fields{
  1578  		topicMap:     make(map[string][]*jocko.Partition),
  1579  		raftCommands: make(chan jocko.RaftCommand),
  1580  		replicators:  make(map[*jocko.Partition]*Replicator),
  1581  		logger:       log.New(),
  1582  		logDir:       "/tmp/jocko",
  1583  		loner:        true,
  1584  		serf:         serf,
  1585  		raft:         raft,
  1586  		brokerAddr:   "localhost:9092",
  1587  		id:           1,
  1588  	}
  1589  }
  1590  
  1591  type nopReaderWriter struct{}
  1592  
  1593  func (nopReaderWriter) Read(b []byte) (int, error)  { return 0, nil }
  1594  func (nopReaderWriter) Write(b []byte) (int, error) { return 0, nil }
  1595  func newNopReaderWriter() io.ReadWriter             { return nopReaderWriter{} }