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{} }