github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/testing/protocoltester.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  /*
    13  the p2p/testing package provides a unit test scheme to check simple
    14  protocol message exchanges with one pivot node and a number of dummy peers
    15  The pivot test node runs a node.Service, the dummy peers run a mock node
    16  that can be used to send and receive messages
    17  */
    18  
    19  package testing
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"strings"
    27  	"sync"
    28  	"testing"
    29  
    30  	"github.com/Sberex/go-sberex/log"
    31  	"github.com/Sberex/go-sberex/node"
    32  	"github.com/Sberex/go-sberex/p2p"
    33  	"github.com/Sberex/go-sberex/p2p/discover"
    34  	"github.com/Sberex/go-sberex/p2p/simulations"
    35  	"github.com/Sberex/go-sberex/p2p/simulations/adapters"
    36  	"github.com/Sberex/go-sberex/rlp"
    37  	"github.com/Sberex/go-sberex/rpc"
    38  )
    39  
    40  // ProtocolTester is the tester environment used for unit testing protocol
    41  // message exchanges. It uses p2p/simulations framework
    42  type ProtocolTester struct {
    43  	*ProtocolSession
    44  	network *simulations.Network
    45  }
    46  
    47  // NewProtocolTester constructs a new ProtocolTester
    48  // it takes as argument the pivot node id, the number of dummy peers and the
    49  // protocol run function called on a peer connection by the p2p server
    50  func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
    51  	services := adapters.Services{
    52  		"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
    53  			return &testNode{run}, nil
    54  		},
    55  		"mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
    56  			return newMockNode(), nil
    57  		},
    58  	}
    59  	adapter := adapters.NewSimAdapter(services)
    60  	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
    61  	if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{
    62  		ID:              id,
    63  		EnableMsgEvents: true,
    64  		Services:        []string{"test"},
    65  	}); err != nil {
    66  		panic(err.Error())
    67  	}
    68  	if err := net.Start(id); err != nil {
    69  		panic(err.Error())
    70  	}
    71  
    72  	node := net.GetNode(id).Node.(*adapters.SimNode)
    73  	peers := make([]*adapters.NodeConfig, n)
    74  	peerIDs := make([]discover.NodeID, n)
    75  	for i := 0; i < n; i++ {
    76  		peers[i] = adapters.RandomNodeConfig()
    77  		peers[i].Services = []string{"mock"}
    78  		peerIDs[i] = peers[i].ID
    79  	}
    80  	events := make(chan *p2p.PeerEvent, 1000)
    81  	node.SubscribeEvents(events)
    82  	ps := &ProtocolSession{
    83  		Server:  node.Server(),
    84  		IDs:     peerIDs,
    85  		adapter: adapter,
    86  		events:  events,
    87  	}
    88  	self := &ProtocolTester{
    89  		ProtocolSession: ps,
    90  		network:         net,
    91  	}
    92  
    93  	self.Connect(id, peers...)
    94  
    95  	return self
    96  }
    97  
    98  // Stop stops the p2p server
    99  func (self *ProtocolTester) Stop() error {
   100  	self.Server.Stop()
   101  	return nil
   102  }
   103  
   104  // Connect brings up the remote peer node and connects it using the
   105  // p2p/simulations network connection with the in memory network adapter
   106  func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
   107  	for _, peer := range peers {
   108  		log.Trace(fmt.Sprintf("start node %v", peer.ID))
   109  		if _, err := self.network.NewNodeWithConfig(peer); err != nil {
   110  			panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
   111  		}
   112  		if err := self.network.Start(peer.ID); err != nil {
   113  			panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
   114  		}
   115  		log.Trace(fmt.Sprintf("connect to %v", peer.ID))
   116  		if err := self.network.Connect(selfID, peer.ID); err != nil {
   117  			panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
   118  		}
   119  	}
   120  
   121  }
   122  
   123  // testNode wraps a protocol run function and implements the node.Service
   124  // interface
   125  type testNode struct {
   126  	run func(*p2p.Peer, p2p.MsgReadWriter) error
   127  }
   128  
   129  func (t *testNode) Protocols() []p2p.Protocol {
   130  	return []p2p.Protocol{{
   131  		Length: 100,
   132  		Run:    t.run,
   133  	}}
   134  }
   135  
   136  func (t *testNode) APIs() []rpc.API {
   137  	return nil
   138  }
   139  
   140  func (t *testNode) Start(server *p2p.Server) error {
   141  	return nil
   142  }
   143  
   144  func (t *testNode) Stop() error {
   145  	return nil
   146  }
   147  
   148  // mockNode is a testNode which doesn't actually run a protocol, instead
   149  // exposing channels so that tests can manually trigger and expect certain
   150  // messages
   151  type mockNode struct {
   152  	testNode
   153  
   154  	trigger  chan *Trigger
   155  	expect   chan []Expect
   156  	err      chan error
   157  	stop     chan struct{}
   158  	stopOnce sync.Once
   159  }
   160  
   161  func newMockNode() *mockNode {
   162  	mock := &mockNode{
   163  		trigger: make(chan *Trigger),
   164  		expect:  make(chan []Expect),
   165  		err:     make(chan error),
   166  		stop:    make(chan struct{}),
   167  	}
   168  	mock.testNode.run = mock.Run
   169  	return mock
   170  }
   171  
   172  // Run is a protocol run function which just loops waiting for tests to
   173  // instruct it to either trigger or expect a message from the peer
   174  func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
   175  	for {
   176  		select {
   177  		case trig := <-m.trigger:
   178  			m.err <- p2p.Send(rw, trig.Code, trig.Msg)
   179  		case exps := <-m.expect:
   180  			m.err <- expectMsgs(rw, exps)
   181  		case <-m.stop:
   182  			return nil
   183  		}
   184  	}
   185  }
   186  
   187  func (m *mockNode) Trigger(trig *Trigger) error {
   188  	m.trigger <- trig
   189  	return <-m.err
   190  }
   191  
   192  func (m *mockNode) Expect(exp ...Expect) error {
   193  	m.expect <- exp
   194  	return <-m.err
   195  }
   196  
   197  func (m *mockNode) Stop() error {
   198  	m.stopOnce.Do(func() { close(m.stop) })
   199  	return nil
   200  }
   201  
   202  func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
   203  	matched := make([]bool, len(exps))
   204  	for {
   205  		msg, err := rw.ReadMsg()
   206  		if err != nil {
   207  			if err == io.EOF {
   208  				break
   209  			}
   210  			return err
   211  		}
   212  		actualContent, err := ioutil.ReadAll(msg.Payload)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		var found bool
   217  		for i, exp := range exps {
   218  			if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(exp.Msg)) {
   219  				if matched[i] {
   220  					return fmt.Errorf("message #%d received two times", i)
   221  				}
   222  				matched[i] = true
   223  				found = true
   224  				break
   225  			}
   226  		}
   227  		if !found {
   228  			expected := make([]string, 0)
   229  			for i, exp := range exps {
   230  				if matched[i] {
   231  					continue
   232  				}
   233  				expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(exp.Msg)))
   234  			}
   235  			return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
   236  		}
   237  		done := true
   238  		for _, m := range matched {
   239  			if !m {
   240  				done = false
   241  				break
   242  			}
   243  		}
   244  		if done {
   245  			return nil
   246  		}
   247  	}
   248  	for i, m := range matched {
   249  		if !m {
   250  			return fmt.Errorf("expected message #%d not received", i)
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  // mustEncodeMsg uses rlp to encode a message.
   257  // In case of error it panics.
   258  func mustEncodeMsg(msg interface{}) []byte {
   259  	contentEnc, err := rlp.EncodeToBytes(msg)
   260  	if err != nil {
   261  		panic("content encode error: " + err.Error())
   262  	}
   263  	return contentEnc
   264  }