github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/testing/protocolsession.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  package testing
    13  
    14  import (
    15  	"errors"
    16  	"fmt"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/Sberex/go-sberex/log"
    21  	"github.com/Sberex/go-sberex/p2p"
    22  	"github.com/Sberex/go-sberex/p2p/discover"
    23  	"github.com/Sberex/go-sberex/p2p/simulations/adapters"
    24  )
    25  
    26  var errTimedOut = errors.New("timed out")
    27  
    28  // ProtocolSession is a quasi simulation of a pivot node running
    29  // a service and a number of dummy peers that can send (trigger) or
    30  // receive (expect) messages
    31  type ProtocolSession struct {
    32  	Server  *p2p.Server
    33  	IDs     []discover.NodeID
    34  	adapter *adapters.SimAdapter
    35  	events  chan *p2p.PeerEvent
    36  }
    37  
    38  // Exchange is the basic units of protocol tests
    39  // the triggers and expects in the arrays are run immediately and asynchronously
    40  // thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
    41  // because it's unpredictable which expect will receive which message
    42  // (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
    43  // an exchange is defined on a session
    44  type Exchange struct {
    45  	Label    string
    46  	Triggers []Trigger
    47  	Expects  []Expect
    48  	Timeout  time.Duration
    49  }
    50  
    51  // Trigger is part of the exchange, incoming message for the pivot node
    52  // sent by a peer
    53  type Trigger struct {
    54  	Msg     interface{}     // type of message to be sent
    55  	Code    uint64          // code of message is given
    56  	Peer    discover.NodeID // the peer to send the message to
    57  	Timeout time.Duration   // timeout duration for the sending
    58  }
    59  
    60  // Expect is part of an exchange, outgoing message from the pivot node
    61  // received by a peer
    62  type Expect struct {
    63  	Msg     interface{}     // type of message to expect
    64  	Code    uint64          // code of message is now given
    65  	Peer    discover.NodeID // the peer that expects the message
    66  	Timeout time.Duration   // timeout duration for receiving
    67  }
    68  
    69  // Disconnect represents a disconnect event, used and checked by TestDisconnected
    70  type Disconnect struct {
    71  	Peer  discover.NodeID // discconnected peer
    72  	Error error           // disconnect reason
    73  }
    74  
    75  // trigger sends messages from peers
    76  func (self *ProtocolSession) trigger(trig Trigger) error {
    77  	simNode, ok := self.adapter.GetNode(trig.Peer)
    78  	if !ok {
    79  		return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.IDs))
    80  	}
    81  	mockNode, ok := simNode.Services()[0].(*mockNode)
    82  	if !ok {
    83  		return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer)
    84  	}
    85  
    86  	errc := make(chan error)
    87  
    88  	go func() {
    89  		errc <- mockNode.Trigger(&trig)
    90  	}()
    91  
    92  	t := trig.Timeout
    93  	if t == time.Duration(0) {
    94  		t = 1000 * time.Millisecond
    95  	}
    96  	select {
    97  	case err := <-errc:
    98  		return err
    99  	case <-time.After(t):
   100  		return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
   101  	}
   102  }
   103  
   104  // expect checks an expectation of a message sent out by the pivot node
   105  func (self *ProtocolSession) expect(exps []Expect) error {
   106  	// construct a map of expectations for each node
   107  	peerExpects := make(map[discover.NodeID][]Expect)
   108  	for _, exp := range exps {
   109  		if exp.Msg == nil {
   110  			return errors.New("no message to expect")
   111  		}
   112  		peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
   113  	}
   114  
   115  	// construct a map of mockNodes for each node
   116  	mockNodes := make(map[discover.NodeID]*mockNode)
   117  	for nodeID := range peerExpects {
   118  		simNode, ok := self.adapter.GetNode(nodeID)
   119  		if !ok {
   120  			return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs))
   121  		}
   122  		mockNode, ok := simNode.Services()[0].(*mockNode)
   123  		if !ok {
   124  			return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
   125  		}
   126  		mockNodes[nodeID] = mockNode
   127  	}
   128  
   129  	// done chanell cancels all created goroutines when function returns
   130  	done := make(chan struct{})
   131  	defer close(done)
   132  	// errc catches the first error from
   133  	errc := make(chan error)
   134  
   135  	wg := &sync.WaitGroup{}
   136  	wg.Add(len(mockNodes))
   137  	for nodeID, mockNode := range mockNodes {
   138  		nodeID := nodeID
   139  		mockNode := mockNode
   140  		go func() {
   141  			defer wg.Done()
   142  
   143  			// Sum all Expect timeouts to give the maximum
   144  			// time for all expectations to finish.
   145  			// mockNode.Expect checks all received messages against
   146  			// a list of expected messages and timeout for each
   147  			// of them can not be checked separately.
   148  			var t time.Duration
   149  			for _, exp := range peerExpects[nodeID] {
   150  				if exp.Timeout == time.Duration(0) {
   151  					t += 2000 * time.Millisecond
   152  				} else {
   153  					t += exp.Timeout
   154  				}
   155  			}
   156  			alarm := time.NewTimer(t)
   157  			defer alarm.Stop()
   158  
   159  			// expectErrc is used to check if error returned
   160  			// from mockNode.Expect is not nil and to send it to
   161  			// errc only in that case.
   162  			// done channel will be closed when function
   163  			expectErrc := make(chan error)
   164  			go func() {
   165  				select {
   166  				case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
   167  				case <-done:
   168  				case <-alarm.C:
   169  				}
   170  			}()
   171  
   172  			select {
   173  			case err := <-expectErrc:
   174  				if err != nil {
   175  					select {
   176  					case errc <- err:
   177  					case <-done:
   178  					case <-alarm.C:
   179  						errc <- errTimedOut
   180  					}
   181  				}
   182  			case <-done:
   183  			case <-alarm.C:
   184  				errc <- errTimedOut
   185  			}
   186  
   187  		}()
   188  	}
   189  
   190  	go func() {
   191  		wg.Wait()
   192  		// close errc when all goroutines finish to return nill err from errc
   193  		close(errc)
   194  	}()
   195  
   196  	return <-errc
   197  }
   198  
   199  // TestExchanges tests a series of exchanges against the session
   200  func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
   201  	for i, e := range exchanges {
   202  		if err := self.testExchange(e); err != nil {
   203  			return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
   204  		}
   205  		log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
   206  	}
   207  	return nil
   208  }
   209  
   210  // testExchange tests a single Exchange.
   211  // Default timeout value is 2 seconds.
   212  func (self *ProtocolSession) testExchange(e Exchange) error {
   213  	errc := make(chan error)
   214  	done := make(chan struct{})
   215  	defer close(done)
   216  
   217  	go func() {
   218  		for _, trig := range e.Triggers {
   219  			err := self.trigger(trig)
   220  			if err != nil {
   221  				errc <- err
   222  				return
   223  			}
   224  		}
   225  
   226  		select {
   227  		case errc <- self.expect(e.Expects):
   228  		case <-done:
   229  		}
   230  	}()
   231  
   232  	// time out globally or finish when all expectations satisfied
   233  	t := e.Timeout
   234  	if t == 0 {
   235  		t = 2000 * time.Millisecond
   236  	}
   237  	alarm := time.NewTimer(t)
   238  	select {
   239  	case err := <-errc:
   240  		return err
   241  	case <-alarm.C:
   242  		return errTimedOut
   243  	}
   244  }
   245  
   246  // TestDisconnected tests the disconnections given as arguments
   247  // the disconnect structs describe what disconnect error is expected on which peer
   248  func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
   249  	expects := make(map[discover.NodeID]error)
   250  	for _, disconnect := range disconnects {
   251  		expects[disconnect.Peer] = disconnect.Error
   252  	}
   253  
   254  	timeout := time.After(time.Second)
   255  	for len(expects) > 0 {
   256  		select {
   257  		case event := <-self.events:
   258  			if event.Type != p2p.PeerEventTypeDrop {
   259  				continue
   260  			}
   261  			expectErr, ok := expects[event.Peer]
   262  			if !ok {
   263  				continue
   264  			}
   265  
   266  			if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
   267  				return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
   268  			}
   269  			delete(expects, event.Peer)
   270  		case <-timeout:
   271  			return fmt.Errorf("timed out waiting for peers to disconnect")
   272  		}
   273  	}
   274  	return nil
   275  }