github.com/xinfinOrg/xdposchain@v1.1.0/p2p/testing/protocoltester.go (about)

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