mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
p2p: Migrate p2p/simulations & p2p/testing to ethersphere/swarm
This commit is contained in:
parent
ae178fd447
commit
338a122995
3 changed files with 0 additions and 633 deletions
|
|
@ -1,67 +0,0 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestPeer interface {
|
|
||||||
ID() enode.ID
|
|
||||||
Drop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPeerPool is an example peerPool to demonstrate registration of peer connections
|
|
||||||
type TestPeerPool struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
peers map[enode.ID]TestPeer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestPeerPool() *TestPeerPool {
|
|
||||||
return &TestPeerPool{peers: make(map[enode.ID]TestPeer)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TestPeerPool) Add(peer TestPeer) {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
log.Trace(fmt.Sprintf("pp add peer %v", peer.ID()))
|
|
||||||
p.peers[peer.ID()] = peer
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TestPeerPool) Remove(peer TestPeer) {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
delete(p.peers, peer.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TestPeerPool) Has(id enode.ID) bool {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
_, ok := p.peers[id]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TestPeerPool) Get(id enode.ID) TestPeer {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
return p.peers[id]
|
|
||||||
}
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errTimedOut = errors.New("timed out")
|
|
||||||
|
|
||||||
// ProtocolSession is a quasi simulation of a pivot node running
|
|
||||||
// a service and a number of dummy peers that can send (trigger) or
|
|
||||||
// receive (expect) messages
|
|
||||||
type ProtocolSession struct {
|
|
||||||
Server *p2p.Server
|
|
||||||
Nodes []*enode.Node
|
|
||||||
adapter *adapters.SimAdapter
|
|
||||||
events chan *p2p.PeerEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange is the basic units of protocol tests
|
|
||||||
// the triggers and expects in the arrays are run immediately and asynchronously
|
|
||||||
// thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
|
|
||||||
// because it's unpredictable which expect will receive which message
|
|
||||||
// (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
|
|
||||||
// an exchange is defined on a session
|
|
||||||
type Exchange struct {
|
|
||||||
Label string
|
|
||||||
Triggers []Trigger
|
|
||||||
Expects []Expect
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger is part of the exchange, incoming message for the pivot node
|
|
||||||
// sent by a peer
|
|
||||||
type Trigger struct {
|
|
||||||
Msg interface{} // type of message to be sent
|
|
||||||
Code uint64 // code of message is given
|
|
||||||
Peer enode.ID // the peer to send the message to
|
|
||||||
Timeout time.Duration // timeout duration for the sending
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expect is part of an exchange, outgoing message from the pivot node
|
|
||||||
// received by a peer
|
|
||||||
type Expect struct {
|
|
||||||
Msg interface{} // type of message to expect
|
|
||||||
Code uint64 // code of message is now given
|
|
||||||
Peer enode.ID // the peer that expects the message
|
|
||||||
Timeout time.Duration // timeout duration for receiving
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect represents a disconnect event, used and checked by TestDisconnected
|
|
||||||
type Disconnect struct {
|
|
||||||
Peer enode.ID // discconnected peer
|
|
||||||
Error error // disconnect reason
|
|
||||||
}
|
|
||||||
|
|
||||||
// trigger sends messages from peers
|
|
||||||
func (s *ProtocolSession) trigger(trig Trigger) error {
|
|
||||||
simNode, ok := s.adapter.GetNode(trig.Peer)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes))
|
|
||||||
}
|
|
||||||
mockNode, ok := simNode.Services()[0].(*mockNode)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code))
|
|
||||||
errc <- mockNode.Trigger(&trig)
|
|
||||||
log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code))
|
|
||||||
}()
|
|
||||||
|
|
||||||
t := trig.Timeout
|
|
||||||
if t == time.Duration(0) {
|
|
||||||
t = 1000 * time.Millisecond
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return err
|
|
||||||
case <-time.After(t):
|
|
||||||
return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect checks an expectation of a message sent out by the pivot node
|
|
||||||
func (s *ProtocolSession) expect(exps []Expect) error {
|
|
||||||
// construct a map of expectations for each node
|
|
||||||
peerExpects := make(map[enode.ID][]Expect)
|
|
||||||
for _, exp := range exps {
|
|
||||||
if exp.Msg == nil {
|
|
||||||
return errors.New("no message to expect")
|
|
||||||
}
|
|
||||||
peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct a map of mockNodes for each node
|
|
||||||
mockNodes := make(map[enode.ID]*mockNode)
|
|
||||||
for nodeID := range peerExpects {
|
|
||||||
simNode, ok := s.adapter.GetNode(nodeID)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes))
|
|
||||||
}
|
|
||||||
mockNode, ok := simNode.Services()[0].(*mockNode)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
|
|
||||||
}
|
|
||||||
mockNodes[nodeID] = mockNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// done chanell cancels all created goroutines when function returns
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
// errc catches the first error from
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
wg.Add(len(mockNodes))
|
|
||||||
for nodeID, mockNode := range mockNodes {
|
|
||||||
nodeID := nodeID
|
|
||||||
mockNode := mockNode
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// Sum all Expect timeouts to give the maximum
|
|
||||||
// time for all expectations to finish.
|
|
||||||
// mockNode.Expect checks all received messages against
|
|
||||||
// a list of expected messages and timeout for each
|
|
||||||
// of them can not be checked separately.
|
|
||||||
var t time.Duration
|
|
||||||
for _, exp := range peerExpects[nodeID] {
|
|
||||||
if exp.Timeout == time.Duration(0) {
|
|
||||||
t += 2000 * time.Millisecond
|
|
||||||
} else {
|
|
||||||
t += exp.Timeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alarm := time.NewTimer(t)
|
|
||||||
defer alarm.Stop()
|
|
||||||
|
|
||||||
// expectErrc is used to check if error returned
|
|
||||||
// from mockNode.Expect is not nil and to send it to
|
|
||||||
// errc only in that case.
|
|
||||||
// done channel will be closed when function
|
|
||||||
expectErrc := make(chan error)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
|
|
||||||
case <-done:
|
|
||||||
case <-alarm.C:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-expectErrc:
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case errc <- err:
|
|
||||||
case <-done:
|
|
||||||
case <-alarm.C:
|
|
||||||
errc <- errTimedOut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-done:
|
|
||||||
case <-alarm.C:
|
|
||||||
errc <- errTimedOut
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
// close errc when all goroutines finish to return nill err from errc
|
|
||||||
close(errc)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return <-errc
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestExchanges tests a series of exchanges against the session
|
|
||||||
func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
|
|
||||||
for i, e := range exchanges {
|
|
||||||
if err := s.testExchange(e); err != nil {
|
|
||||||
return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
|
|
||||||
}
|
|
||||||
log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testExchange tests a single Exchange.
|
|
||||||
// Default timeout value is 2 seconds.
|
|
||||||
func (s *ProtocolSession) testExchange(e Exchange) error {
|
|
||||||
errc := make(chan error)
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for _, trig := range e.Triggers {
|
|
||||||
err := s.trigger(trig)
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case errc <- s.expect(e.Expects):
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// time out globally or finish when all expectations satisfied
|
|
||||||
t := e.Timeout
|
|
||||||
if t == 0 {
|
|
||||||
t = 2000 * time.Millisecond
|
|
||||||
}
|
|
||||||
alarm := time.NewTimer(t)
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return err
|
|
||||||
case <-alarm.C:
|
|
||||||
return errTimedOut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDisconnected tests the disconnections given as arguments
|
|
||||||
// the disconnect structs describe what disconnect error is expected on which peer
|
|
||||||
func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
|
|
||||||
expects := make(map[enode.ID]error)
|
|
||||||
for _, disconnect := range disconnects {
|
|
||||||
expects[disconnect.Peer] = disconnect.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.After(time.Second)
|
|
||||||
for len(expects) > 0 {
|
|
||||||
select {
|
|
||||||
case event := <-s.events:
|
|
||||||
if event.Type != p2p.PeerEventTypeDrop {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expectErr, ok := expects[event.Peer]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
|
|
||||||
return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
|
|
||||||
}
|
|
||||||
delete(expects, event.Peer)
|
|
||||||
case <-timeout:
|
|
||||||
return fmt.Errorf("timed out waiting for peers to disconnect")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,284 +0,0 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
/*
|
|
||||||
the p2p/testing package provides a unit test scheme to check simple
|
|
||||||
protocol message exchanges with one pivot node and a number of dummy peers
|
|
||||||
The pivot test node runs a node.Service, the dummy peers run a mock node
|
|
||||||
that can be used to send and receive messages
|
|
||||||
*/
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProtocolTester is the tester environment used for unit testing protocol
|
|
||||||
// message exchanges. It uses p2p/simulations framework
|
|
||||||
type ProtocolTester struct {
|
|
||||||
*ProtocolSession
|
|
||||||
network *simulations.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProtocolTester constructs a new ProtocolTester
|
|
||||||
// it takes as argument the pivot node id, the number of dummy peers and the
|
|
||||||
// protocol run function called on a peer connection by the p2p server
|
|
||||||
func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
|
|
||||||
services := adapters.Services{
|
|
||||||
"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
|
|
||||||
return &testNode{run}, nil
|
|
||||||
},
|
|
||||||
"mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
|
|
||||||
return newMockNode(), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
adapter := adapters.NewSimAdapter(services)
|
|
||||||
net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
|
|
||||||
nodeConfig := &adapters.NodeConfig{
|
|
||||||
PrivateKey: prvkey,
|
|
||||||
EnableMsgEvents: true,
|
|
||||||
Services: []string{"test"},
|
|
||||||
}
|
|
||||||
if _, err := net.NewNodeWithConfig(nodeConfig); err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
if err := net.Start(nodeConfig.ID); err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
node := net.GetNode(nodeConfig.ID).Node.(*adapters.SimNode)
|
|
||||||
peers := make([]*adapters.NodeConfig, nodeCount)
|
|
||||||
nodes := make([]*enode.Node, nodeCount)
|
|
||||||
for i := 0; i < nodeCount; i++ {
|
|
||||||
peers[i] = adapters.RandomNodeConfig()
|
|
||||||
peers[i].Services = []string{"mock"}
|
|
||||||
if _, err := net.NewNodeWithConfig(peers[i]); err != nil {
|
|
||||||
panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err))
|
|
||||||
}
|
|
||||||
if err := net.Start(peers[i].ID); err != nil {
|
|
||||||
panic(fmt.Sprintf("error starting peer %v: %v", peers[i].ID, err))
|
|
||||||
}
|
|
||||||
nodes[i] = peers[i].Node()
|
|
||||||
}
|
|
||||||
events := make(chan *p2p.PeerEvent, 1000)
|
|
||||||
node.SubscribeEvents(events)
|
|
||||||
ps := &ProtocolSession{
|
|
||||||
Server: node.Server(),
|
|
||||||
Nodes: nodes,
|
|
||||||
adapter: adapter,
|
|
||||||
events: events,
|
|
||||||
}
|
|
||||||
self := &ProtocolTester{
|
|
||||||
ProtocolSession: ps,
|
|
||||||
network: net,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.Connect(nodeConfig.ID, peers...)
|
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the p2p server
|
|
||||||
func (t *ProtocolTester) Stop() {
|
|
||||||
t.Server.Stop()
|
|
||||||
t.network.Shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect brings up the remote peer node and connects it using the
|
|
||||||
// p2p/simulations network connection with the in memory network adapter
|
|
||||||
func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) {
|
|
||||||
for _, peer := range peers {
|
|
||||||
log.Trace(fmt.Sprintf("connect to %v", peer.ID))
|
|
||||||
if err := t.network.Connect(selfID, peer.ID); err != nil {
|
|
||||||
panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// testNode wraps a protocol run function and implements the node.Service
|
|
||||||
// interface
|
|
||||||
type testNode struct {
|
|
||||||
run func(*p2p.Peer, p2p.MsgReadWriter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testNode) Protocols() []p2p.Protocol {
|
|
||||||
return []p2p.Protocol{{
|
|
||||||
Length: 100,
|
|
||||||
Run: t.run,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testNode) APIs() []rpc.API {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testNode) Start(server *p2p.Server) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testNode) Stop() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mockNode is a testNode which doesn't actually run a protocol, instead
|
|
||||||
// exposing channels so that tests can manually trigger and expect certain
|
|
||||||
// messages
|
|
||||||
type mockNode struct {
|
|
||||||
testNode
|
|
||||||
|
|
||||||
trigger chan *Trigger
|
|
||||||
expect chan []Expect
|
|
||||||
err chan error
|
|
||||||
stop chan struct{}
|
|
||||||
stopOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockNode() *mockNode {
|
|
||||||
mock := &mockNode{
|
|
||||||
trigger: make(chan *Trigger),
|
|
||||||
expect: make(chan []Expect),
|
|
||||||
err: make(chan error),
|
|
||||||
stop: make(chan struct{}),
|
|
||||||
}
|
|
||||||
mock.testNode.run = mock.Run
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run is a protocol run function which just loops waiting for tests to
|
|
||||||
// instruct it to either trigger or expect a message from the peer
|
|
||||||
func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case trig := <-m.trigger:
|
|
||||||
wmsg := Wrap(trig.Msg)
|
|
||||||
m.err <- p2p.Send(rw, trig.Code, wmsg)
|
|
||||||
case exps := <-m.expect:
|
|
||||||
m.err <- expectMsgs(rw, exps)
|
|
||||||
case <-m.stop:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockNode) Trigger(trig *Trigger) error {
|
|
||||||
m.trigger <- trig
|
|
||||||
return <-m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockNode) Expect(exp ...Expect) error {
|
|
||||||
m.expect <- exp
|
|
||||||
return <-m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockNode) Stop() error {
|
|
||||||
m.stopOnce.Do(func() { close(m.stop) })
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
|
|
||||||
matched := make([]bool, len(exps))
|
|
||||||
for {
|
|
||||||
msg, err := rw.ReadMsg()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
actualContent, err := ioutil.ReadAll(msg.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var found bool
|
|
||||||
for i, exp := range exps {
|
|
||||||
if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(Wrap(exp.Msg))) {
|
|
||||||
if matched[i] {
|
|
||||||
return fmt.Errorf("message #%d received two times", i)
|
|
||||||
}
|
|
||||||
matched[i] = true
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
expected := make([]string, 0)
|
|
||||||
for i, exp := range exps {
|
|
||||||
if matched[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(Wrap(exp.Msg))))
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
|
|
||||||
}
|
|
||||||
done := true
|
|
||||||
for _, m := range matched {
|
|
||||||
if !m {
|
|
||||||
done = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, m := range matched {
|
|
||||||
if !m {
|
|
||||||
return fmt.Errorf("expected message #%d not received", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustEncodeMsg uses rlp to encode a message.
|
|
||||||
// In case of error it panics.
|
|
||||||
func mustEncodeMsg(msg interface{}) []byte {
|
|
||||||
contentEnc, err := rlp.EncodeToBytes(msg)
|
|
||||||
if err != nil {
|
|
||||||
panic("content encode error: " + err.Error())
|
|
||||||
}
|
|
||||||
return contentEnc
|
|
||||||
}
|
|
||||||
|
|
||||||
type WrappedMsg struct {
|
|
||||||
Context []byte
|
|
||||||
Size uint32
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func Wrap(msg interface{}) interface{} {
|
|
||||||
data, _ := rlp.EncodeToBytes(msg)
|
|
||||||
return &WrappedMsg{
|
|
||||||
Size: uint32(len(data)),
|
|
||||||
Payload: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue