github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/testing/protocolsession.go (about)

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