github.com/aquanetwork/aquachain@v1.7.8/p2p/testing/protocoltester.go (about)

     1  // Copyright 2017 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain 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 aquachain 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 aquachain 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  	"testing"
    34  
    35  	"gitlab.com/aquachain/aquachain/common/log"
    36  	"gitlab.com/aquachain/aquachain/node"
    37  	"gitlab.com/aquachain/aquachain/p2p"
    38  	"gitlab.com/aquachain/aquachain/p2p/discover"
    39  	"gitlab.com/aquachain/aquachain/p2p/simulations"
    40  	"gitlab.com/aquachain/aquachain/p2p/simulations/adapters"
    41  	"gitlab.com/aquachain/aquachain/rlp"
    42  	"gitlab.com/aquachain/aquachain/rpc"
    43  )
    44  
    45  // ProtocolTester is the tester environment used for unit testing protocol
    46  // message exchanges. It uses p2p/simulations framework
    47  type ProtocolTester struct {
    48  	*ProtocolSession
    49  	network *simulations.Network
    50  }
    51  
    52  // NewProtocolTester constructs a new ProtocolTester
    53  // it takes as argument the pivot node id, the number of dummy peers and the
    54  // protocol run function called on a peer connection by the p2p server
    55  func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
    56  	services := adapters.Services{
    57  		"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
    58  			return &testNode{run}, nil
    59  		},
    60  		"mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
    61  			return newMockNode(), nil
    62  		},
    63  	}
    64  	adapter := adapters.NewSimAdapter(services)
    65  	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
    66  	if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{
    67  		ID:              id,
    68  		EnableMsgEvents: true,
    69  		Services:        []string{"test"},
    70  	}); err != nil {
    71  		panic(err.Error())
    72  	}
    73  	if err := net.Start(id); err != nil {
    74  		panic(err.Error())
    75  	}
    76  
    77  	node := net.GetNode(id).Node.(*adapters.SimNode)
    78  	peers := make([]*adapters.NodeConfig, n)
    79  	peerIDs := make([]discover.NodeID, n)
    80  	for i := 0; i < n; i++ {
    81  		peers[i] = adapters.RandomNodeConfig()
    82  		peers[i].Services = []string{"mock"}
    83  		peerIDs[i] = peers[i].ID
    84  	}
    85  	events := make(chan *p2p.PeerEvent, 1000)
    86  	node.SubscribeEvents(events)
    87  	ps := &ProtocolSession{
    88  		Server:  node.Server(),
    89  		IDs:     peerIDs,
    90  		adapter: adapter,
    91  		events:  events,
    92  	}
    93  	self := &ProtocolTester{
    94  		ProtocolSession: ps,
    95  		network:         net,
    96  	}
    97  
    98  	self.Connect(id, peers...)
    99  
   100  	return self
   101  }
   102  
   103  // Stop stops the p2p server
   104  func (self *ProtocolTester) Stop() error {
   105  	self.Server.Stop()
   106  	return nil
   107  }
   108  
   109  // Connect brings up the remote peer node and connects it using the
   110  // p2p/simulations network connection with the in memory network adapter
   111  func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
   112  	for _, peer := range peers {
   113  		log.Trace(fmt.Sprintf("start node %v", peer.ID))
   114  		if _, err := self.network.NewNodeWithConfig(peer); err != nil {
   115  			panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
   116  		}
   117  		if err := self.network.Start(peer.ID); err != nil {
   118  			panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
   119  		}
   120  		log.Trace(fmt.Sprintf("connect to %v", peer.ID))
   121  		if err := self.network.Connect(selfID, peer.ID); err != nil {
   122  			panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
   123  		}
   124  	}
   125  
   126  }
   127  
   128  // testNode wraps a protocol run function and implements the node.Service
   129  // interface
   130  type testNode struct {
   131  	run func(*p2p.Peer, p2p.MsgReadWriter) error
   132  }
   133  
   134  func (t *testNode) Protocols() []p2p.Protocol {
   135  	return []p2p.Protocol{{
   136  		Length: 100,
   137  		Run:    t.run,
   138  	}}
   139  }
   140  
   141  func (t *testNode) APIs() []rpc.API {
   142  	return nil
   143  }
   144  
   145  func (t *testNode) Start(server *p2p.Server) error {
   146  	return nil
   147  }
   148  
   149  func (t *testNode) Stop() error {
   150  	return nil
   151  }
   152  
   153  // mockNode is a testNode which doesn't actually run a protocol, instead
   154  // exposing channels so that tests can manually trigger and expect certain
   155  // messages
   156  type mockNode struct {
   157  	testNode
   158  
   159  	trigger  chan *Trigger
   160  	expect   chan []Expect
   161  	err      chan error
   162  	stop     chan struct{}
   163  	stopOnce sync.Once
   164  }
   165  
   166  func newMockNode() *mockNode {
   167  	mock := &mockNode{
   168  		trigger: make(chan *Trigger),
   169  		expect:  make(chan []Expect),
   170  		err:     make(chan error),
   171  		stop:    make(chan struct{}),
   172  	}
   173  	mock.testNode.run = mock.Run
   174  	return mock
   175  }
   176  
   177  // Run is a protocol run function which just loops waiting for tests to
   178  // instruct it to either trigger or expect a message from the peer
   179  func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
   180  	for {
   181  		select {
   182  		case trig := <-m.trigger:
   183  			m.err <- p2p.Send(rw, trig.Code, trig.Msg)
   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(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(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  }