github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/p2p/switch_test.go (about) 1 package p2p 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "sync" 10 "sync/atomic" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" 18 "github.com/gnolang/gno/tm2/pkg/log" 19 "github.com/gnolang/gno/tm2/pkg/p2p/config" 20 "github.com/gnolang/gno/tm2/pkg/p2p/conn" 21 "github.com/gnolang/gno/tm2/pkg/testutils" 22 ) 23 24 var cfg *config.P2PConfig 25 26 func init() { 27 cfg = config.DefaultP2PConfig() 28 cfg.PexReactor = true 29 cfg.AllowDuplicateIP = true 30 } 31 32 type PeerMessage struct { 33 PeerID ID 34 Bytes []byte 35 Counter int 36 } 37 38 type TestReactor struct { 39 BaseReactor 40 41 mtx sync.Mutex 42 channels []*conn.ChannelDescriptor 43 logMessages bool 44 msgsCounter int 45 msgsReceived map[byte][]PeerMessage 46 } 47 48 func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { 49 tr := &TestReactor{ 50 channels: channels, 51 logMessages: logMessages, 52 msgsReceived: make(map[byte][]PeerMessage), 53 } 54 tr.BaseReactor = *NewBaseReactor("TestReactor", tr) 55 tr.SetLogger(log.NewNoopLogger()) 56 return tr 57 } 58 59 func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { 60 return tr.channels 61 } 62 63 func (tr *TestReactor) AddPeer(peer Peer) {} 64 65 func (tr *TestReactor) RemovePeer(peer Peer, reason interface{}) {} 66 67 func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { 68 if tr.logMessages { 69 tr.mtx.Lock() 70 defer tr.mtx.Unlock() 71 // fmt.Printf("Received: %X, %X\n", chID, msgBytes) 72 tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) 73 tr.msgsCounter++ 74 } 75 } 76 77 func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { 78 tr.mtx.Lock() 79 defer tr.mtx.Unlock() 80 return tr.msgsReceived[chID] 81 } 82 83 // ----------------------------------------------------------------------------- 84 85 // convenience method for creating two switches connected to each other. 86 // XXX: note this uses net.Pipe and not a proper TCP conn 87 func MakeSwitchPair(_ testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { 88 // Create two switches that will be interconnected. 89 switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) 90 return switches[0], switches[1] 91 } 92 93 func initSwitchFunc(i int, sw *Switch) *Switch { 94 // Make two reactors of two channels each 95 sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ 96 {ID: byte(0x00), Priority: 10}, 97 {ID: byte(0x01), Priority: 10}, 98 }, true)) 99 sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ 100 {ID: byte(0x02), Priority: 10}, 101 {ID: byte(0x03), Priority: 10}, 102 }, true)) 103 104 return sw 105 } 106 107 func TestSwitches(t *testing.T) { 108 t.Parallel() 109 110 s1, s2 := MakeSwitchPair(t, initSwitchFunc) 111 defer s1.Stop() 112 defer s2.Stop() 113 114 if s1.Peers().Size() != 1 { 115 t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size()) 116 } 117 if s2.Peers().Size() != 1 { 118 t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size()) 119 } 120 121 // Lets send some messages 122 ch0Msg := []byte("channel zero") 123 ch1Msg := []byte("channel foo") 124 ch2Msg := []byte("channel bar") 125 126 s1.Broadcast(byte(0x00), ch0Msg) 127 s1.Broadcast(byte(0x01), ch1Msg) 128 s1.Broadcast(byte(0x02), ch2Msg) 129 130 assertMsgReceivedWithTimeout(t, ch0Msg, byte(0x00), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) 131 assertMsgReceivedWithTimeout(t, ch1Msg, byte(0x01), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) 132 assertMsgReceivedWithTimeout(t, ch2Msg, byte(0x02), s2.Reactor("bar").(*TestReactor), 10*time.Millisecond, 5*time.Second) 133 } 134 135 func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, reactor *TestReactor, checkPeriod, timeout time.Duration) { 136 t.Helper() 137 138 ticker := time.NewTicker(checkPeriod) 139 for { 140 select { 141 case <-ticker.C: 142 msgs := reactor.getMsgs(channel) 143 if len(msgs) > 0 { 144 if !bytes.Equal(msgs[0].Bytes, msgBytes) { 145 t.Fatalf("Unexpected message bytes. Wanted: %X, Got: %X", msgBytes, msgs[0].Bytes) 146 } 147 return 148 } 149 150 case <-time.After(timeout): 151 t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) 152 } 153 } 154 } 155 156 func TestSwitchFiltersOutItself(t *testing.T) { 157 t.Parallel() 158 159 s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) 160 161 // simulate s1 having a public IP by creating a remote peer with the same ID 162 rp := &remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} 163 rp.Start() 164 165 // addr should be rejected in addPeer based on the same ID 166 err := s1.DialPeerWithAddress(rp.Addr()) 167 if assert.Error(t, err) { 168 if err, ok := err.(RejectedError); ok { 169 if !err.IsSelf() { 170 t.Errorf("expected self to be rejected") 171 } 172 } else { 173 t.Errorf("expected RejectedError") 174 } 175 } 176 177 rp.Stop() 178 179 assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond) 180 } 181 182 func TestSwitchPeerFilter(t *testing.T) { 183 t.Parallel() 184 185 var ( 186 filters = []PeerFilterFunc{ 187 func(_ IPeerSet, _ Peer) error { return nil }, 188 func(_ IPeerSet, _ Peer) error { return fmt.Errorf("denied!") }, 189 func(_ IPeerSet, _ Peer) error { return nil }, 190 } 191 sw = MakeSwitch( 192 cfg, 193 1, 194 "testing", 195 "123.123.123", 196 initSwitchFunc, 197 SwitchPeerFilters(filters...), 198 ) 199 ) 200 defer sw.Stop() 201 202 // simulate remote peer 203 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 204 rp.Start() 205 defer rp.Stop() 206 207 p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ 208 chDescs: sw.chDescs, 209 onPeerError: sw.StopPeerForError, 210 isPersistent: sw.isPeerPersistentFn(), 211 reactorsByCh: sw.reactorsByCh, 212 }) 213 if err != nil { 214 t.Fatal(err) 215 } 216 217 err = sw.addPeer(p) 218 if err, ok := err.(RejectedError); ok { 219 if !err.IsFiltered() { 220 t.Errorf("expected peer to be filtered") 221 } 222 } else { 223 t.Errorf("expected RejectedError") 224 } 225 } 226 227 func TestSwitchPeerFilterTimeout(t *testing.T) { 228 t.Parallel() 229 230 var ( 231 filters = []PeerFilterFunc{ 232 func(_ IPeerSet, _ Peer) error { 233 time.Sleep(10 * time.Millisecond) 234 return nil 235 }, 236 } 237 sw = MakeSwitch( 238 cfg, 239 1, 240 "testing", 241 "123.123.123", 242 initSwitchFunc, 243 SwitchFilterTimeout(5*time.Millisecond), 244 SwitchPeerFilters(filters...), 245 ) 246 ) 247 defer sw.Stop() 248 249 // simulate remote peer 250 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 251 rp.Start() 252 defer rp.Stop() 253 254 p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ 255 chDescs: sw.chDescs, 256 onPeerError: sw.StopPeerForError, 257 isPersistent: sw.isPeerPersistentFn(), 258 reactorsByCh: sw.reactorsByCh, 259 }) 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 err = sw.addPeer(p) 265 if _, ok := err.(FilterTimeoutError); !ok { 266 t.Errorf("expected FilterTimeoutError") 267 } 268 } 269 270 func TestSwitchPeerFilterDuplicate(t *testing.T) { 271 t.Parallel() 272 273 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 274 sw.Start() 275 defer sw.Stop() 276 277 // simulate remote peer 278 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 279 rp.Start() 280 defer rp.Stop() 281 282 p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ 283 chDescs: sw.chDescs, 284 onPeerError: sw.StopPeerForError, 285 isPersistent: sw.isPeerPersistentFn(), 286 reactorsByCh: sw.reactorsByCh, 287 }) 288 if err != nil { 289 t.Fatal(err) 290 } 291 292 if err := sw.addPeer(p); err != nil { 293 t.Fatal(err) 294 } 295 296 err = sw.addPeer(p) 297 if errRej, ok := err.(RejectedError); ok { 298 if !errRej.IsDuplicate() { 299 t.Errorf("expected peer to be duplicate. got %v", errRej) 300 } 301 } else { 302 t.Errorf("expected RejectedError, got %v", err) 303 } 304 } 305 306 func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) { 307 t.Helper() 308 309 time.Sleep(timeout) 310 if sw.Peers().Size() != 0 { 311 t.Fatalf("Expected %v to not connect to some peers, got %d", sw, sw.Peers().Size()) 312 } 313 } 314 315 func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { 316 t.Parallel() 317 318 assert, require := assert.New(t), require.New(t) 319 320 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 321 err := sw.Start() 322 if err != nil { 323 t.Error(err) 324 } 325 defer sw.Stop() 326 327 // simulate remote peer 328 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 329 rp.Start() 330 defer rp.Stop() 331 332 p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ 333 chDescs: sw.chDescs, 334 onPeerError: sw.StopPeerForError, 335 isPersistent: sw.isPeerPersistentFn(), 336 reactorsByCh: sw.reactorsByCh, 337 }) 338 require.Nil(err) 339 340 err = sw.addPeer(p) 341 require.Nil(err) 342 343 require.NotNil(sw.Peers().Get(rp.ID())) 344 345 // simulate failure by closing connection 346 p.(*peer).CloseConn() 347 348 assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond) 349 assert.False(p.IsRunning()) 350 } 351 352 func TestSwitchStopPeerForError(t *testing.T) { 353 t.Parallel() 354 355 // make two connected switches 356 sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { 357 return initSwitchFunc(i, sw) 358 }) 359 360 assert.Equal(t, len(sw1.Peers().List()), 1) 361 362 // send messages to the peer from sw1 363 p := sw1.Peers().List()[0] 364 p.Send(0x1, []byte("here's a message to send")) 365 366 // stop sw2. this should cause the p to fail, 367 // which results in calling StopPeerForError internally 368 sw2.Stop() 369 370 // now call StopPeerForError explicitly, eg. from a reactor 371 sw1.StopPeerForError(p, fmt.Errorf("some err")) 372 373 assert.Equal(t, len(sw1.Peers().List()), 0) 374 } 375 376 func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { 377 t.Parallel() 378 379 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 380 err := sw.Start() 381 require.NoError(t, err) 382 defer sw.Stop() 383 384 // 1. simulate failure by closing connection 385 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 386 rp.Start() 387 defer rp.Stop() 388 389 err = sw.AddPersistentPeers([]string{rp.Addr().String()}) 390 require.NoError(t, err) 391 392 err = sw.DialPeerWithAddress(rp.Addr()) 393 require.Nil(t, err) 394 require.NotNil(t, sw.Peers().Get(rp.ID())) 395 396 p := sw.Peers().List()[0] 397 p.(*peer).CloseConn() 398 399 waitUntilSwitchHasAtLeastNPeers(sw, 1) 400 assert.False(t, p.IsRunning()) // old peer instance 401 assert.Equal(t, 1, sw.Peers().Size()) // new peer instance 402 403 // 2. simulate first time dial failure 404 rp = &remotePeer{ 405 PrivKey: ed25519.GenPrivKey(), 406 Config: cfg, 407 // Use different interface to prevent duplicate IP filter, this will break 408 // beyond two peers. 409 listenAddr: "127.0.0.1:0", 410 } 411 rp.Start() 412 defer rp.Stop() 413 414 conf := config.DefaultP2PConfig() 415 conf.TestDialFail = true // will trigger a reconnect 416 err = sw.addOutboundPeerWithConfig(rp.Addr(), conf) 417 require.NotNil(t, err) 418 // DialPeerWithAddres - sw.peerConfig resets the dialer 419 waitUntilSwitchHasAtLeastNPeers(sw, 2) 420 assert.Equal(t, 2, sw.Peers().Size()) 421 } 422 423 func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { 424 t.Parallel() 425 426 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 427 err := sw.Start() 428 require.NoError(t, err) 429 defer sw.Stop() 430 431 // 1. simulate failure by closing the connection 432 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 433 rp.Start() 434 defer rp.Stop() 435 436 err = sw.AddPersistentPeers([]string{rp.Addr().String()}) 437 require.NoError(t, err) 438 439 conn, err := rp.Dial(sw.NetAddress()) 440 require.NoError(t, err) 441 time.Sleep(100 * time.Millisecond) 442 require.NotNil(t, sw.Peers().Get(rp.ID())) 443 444 conn.Close() 445 446 waitUntilSwitchHasAtLeastNPeers(sw, 1) 447 assert.Equal(t, 1, sw.Peers().Size()) 448 } 449 450 func TestSwitchDialPeersAsync(t *testing.T) { 451 t.Parallel() 452 453 if testing.Short() { 454 return 455 } 456 457 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 458 err := sw.Start() 459 require.NoError(t, err) 460 defer sw.Stop() 461 462 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 463 rp.Start() 464 defer rp.Stop() 465 466 err = sw.DialPeersAsync([]string{rp.Addr().String()}) 467 require.NoError(t, err) 468 time.Sleep(dialRandomizerIntervalMilliseconds * time.Millisecond) 469 require.NotNil(t, sw.Peers().Get(rp.ID())) 470 } 471 472 func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { 473 for i := 0; i < 20; i++ { 474 time.Sleep(250 * time.Millisecond) 475 has := sw.Peers().Size() 476 if has >= n { 477 break 478 } 479 } 480 } 481 482 func TestSwitchFullConnectivity(t *testing.T) { 483 t.Parallel() 484 485 switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) 486 defer func() { 487 for _, sw := range switches { 488 sw.Stop() 489 } 490 }() 491 492 for i, sw := range switches { 493 if sw.Peers().Size() != 2 { 494 t.Fatalf("Expected each switch to be connected to 2 other, but %d switch only connected to %d", sw.Peers().Size(), i) 495 } 496 } 497 } 498 499 func TestSwitchAcceptRoutine(t *testing.T) { 500 t.Parallel() 501 502 cfg.MaxNumInboundPeers = 5 503 504 // make switch 505 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) 506 err := sw.Start() 507 require.NoError(t, err) 508 defer sw.Stop() 509 510 remotePeers := make([]*remotePeer, 0) 511 assert.Equal(t, 0, sw.Peers().Size()) 512 513 // 1. check we connect up to MaxNumInboundPeers 514 for i := 0; i < cfg.MaxNumInboundPeers; i++ { 515 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 516 remotePeers = append(remotePeers, rp) 517 rp.Start() 518 c, err := rp.Dial(sw.NetAddress()) 519 require.NoError(t, err) 520 // spawn a reading routine to prevent connection from closing 521 go func(c net.Conn) { 522 for { 523 one := make([]byte, 1) 524 _, err := c.Read(one) 525 if err != nil { 526 return 527 } 528 } 529 }(c) 530 } 531 time.Sleep(100 * time.Millisecond) 532 assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) 533 534 // 2. check we close new connections if we already have MaxNumInboundPeers peers 535 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 536 rp.Start() 537 conn, err := rp.Dial(sw.NetAddress()) 538 require.NoError(t, err) 539 // check conn is closed 540 one := make([]byte, 1) 541 conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) 542 _, err = conn.Read(one) 543 assert.Equal(t, io.EOF, err) 544 assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) 545 rp.Stop() 546 547 // stop remote peers 548 for _, rp := range remotePeers { 549 rp.Stop() 550 } 551 } 552 553 type errorTransport struct { 554 acceptErr error 555 } 556 557 func (et errorTransport) NetAddress() NetAddress { 558 panic("not implemented") 559 } 560 561 func (et errorTransport) Accept(c peerConfig) (Peer, error) { 562 return nil, et.acceptErr 563 } 564 565 func (errorTransport) Dial(NetAddress, peerConfig) (Peer, error) { 566 panic("not implemented") 567 } 568 569 func (errorTransport) Cleanup(Peer) { 570 panic("not implemented") 571 } 572 573 func TestSwitchAcceptRoutineErrorCases(t *testing.T) { 574 t.Parallel() 575 576 sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) 577 assert.NotPanics(t, func() { 578 err := sw.Start() 579 assert.NoError(t, err) 580 sw.Stop() 581 }) 582 583 sw = NewSwitch(cfg, errorTransport{RejectedError{conn: nil, err: errors.New("filtered"), isFiltered: true}}) 584 assert.NotPanics(t, func() { 585 err := sw.Start() 586 assert.NoError(t, err) 587 sw.Stop() 588 }) 589 590 sw = NewSwitch(cfg, errorTransport{TransportClosedError{}}) 591 assert.NotPanics(t, func() { 592 err := sw.Start() 593 assert.NoError(t, err) 594 sw.Stop() 595 }) 596 } 597 598 // mockReactor checks that InitPeer never called before RemovePeer. If that's 599 // not true, InitCalledBeforeRemoveFinished will return true. 600 type mockReactor struct { 601 *BaseReactor 602 603 // atomic 604 removePeerInProgress uint32 605 initCalledBeforeRemoveFinished uint32 606 } 607 608 func (r *mockReactor) RemovePeer(peer Peer, reason interface{}) { 609 atomic.StoreUint32(&r.removePeerInProgress, 1) 610 defer atomic.StoreUint32(&r.removePeerInProgress, 0) 611 time.Sleep(100 * time.Millisecond) 612 } 613 614 func (r *mockReactor) InitPeer(peer Peer) Peer { 615 if atomic.LoadUint32(&r.removePeerInProgress) == 1 { 616 atomic.StoreUint32(&r.initCalledBeforeRemoveFinished, 1) 617 } 618 619 return peer 620 } 621 622 func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { 623 return atomic.LoadUint32(&r.initCalledBeforeRemoveFinished) == 1 624 } 625 626 // see stopAndRemovePeer 627 func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { 628 t.Parallel() 629 630 testutils.FilterStability(t, testutils.Flappy) 631 632 // make reactor 633 reactor := &mockReactor{} 634 reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) 635 636 // make switch 637 sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *Switch) *Switch { 638 sw.AddReactor("mock", reactor) 639 return sw 640 }) 641 err := sw.Start() 642 require.NoError(t, err) 643 defer sw.Stop() 644 645 // add peer 646 rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} 647 rp.Start() 648 defer rp.Stop() 649 _, err = rp.Dial(sw.NetAddress()) 650 require.NoError(t, err) 651 // wait till the switch adds rp to the peer set 652 time.Sleep(100 * time.Millisecond) 653 654 // stop peer asynchronously 655 go sw.StopPeerForError(sw.Peers().Get(rp.ID()), "test") 656 657 // simulate peer reconnecting to us 658 _, err = rp.Dial(sw.NetAddress()) 659 require.NoError(t, err) 660 // wait till the switch adds rp to the peer set 661 time.Sleep(100 * time.Millisecond) 662 663 // make sure reactor.RemovePeer is finished before InitPeer is called 664 assert.False(t, reactor.InitCalledBeforeRemoveFinished()) 665 } 666 667 func BenchmarkSwitchBroadcast(b *testing.B) { 668 s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { 669 // Make bar reactors of bar channels each 670 sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ 671 {ID: byte(0x00), Priority: 10}, 672 {ID: byte(0x01), Priority: 10}, 673 }, false)) 674 sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ 675 {ID: byte(0x02), Priority: 10}, 676 {ID: byte(0x03), Priority: 10}, 677 }, false)) 678 return sw 679 }) 680 defer s1.Stop() 681 defer s2.Stop() 682 683 // Allow time for goroutines to boot up 684 time.Sleep(1 * time.Second) 685 686 b.ResetTimer() 687 688 numSuccess, numFailure := 0, 0 689 690 // Send random message from foo channel to another 691 for i := 0; i < b.N; i++ { 692 chID := byte(i % 4) 693 successChan := s1.Broadcast(chID, []byte("test data")) 694 for s := range successChan { 695 if s { 696 numSuccess++ 697 } else { 698 numFailure++ 699 } 700 } 701 } 702 703 b.Logf("success: %v, failure: %v", numSuccess, numFailure) 704 }