mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
subpackages: * adapters: * msgpipes for simulated test connections * rlpx the RLPx adapter for normal non-test use * simnet simulated in-process network adapter * protocols: easy-to-setup protocols * simulations: * simulated network using simnet adapter * (local cluster of nodes running their own p2p server) * (remote cluster of nodes managed via docker) * testing: test resource drivers * exchange: trigger/expect style driver for single node and its peers * sessions: for unit testing protocols and protocol modules * (network: for network testing, benchmarking, stats, correctness, fault tolerance) see more in the README-s in each subpackage
304 lines
8.2 KiB
Go
304 lines
8.2 KiB
Go
// Package protocols helpers_test make it easier to
|
|
// write protocol tests by providing convenience functions and structures
|
|
// protocols uses these helpers for its own tests
|
|
// but ideally should sit in p2p/protocols/testing/ subpackage
|
|
package testing
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
"github.com/ethereum/go-ethereum/p2p"
|
|
"github.com/ethereum/go-ethereum/p2p/adapters"
|
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
)
|
|
|
|
// ExchangeTestSession assumes a network with a protocol running on multiple peer connection
|
|
// and is used to test scanarios of message exchange among a select array of nodes
|
|
// the scenarios are sets of exchanges, each with a trigger and an expectation
|
|
// This rigid regime is suitable for
|
|
// * unit testing protocol message exchanges (nodes are peers of a local node)
|
|
// * testing routed messaging between remote non-connected nodes within a group
|
|
type ExchangeTestSession struct {
|
|
lock sync.Mutex
|
|
IDs []*discover.NodeID
|
|
TestNetAdapter
|
|
TestMessenger
|
|
t *testing.T
|
|
}
|
|
|
|
// implemented by simulations/
|
|
type TestNetAdapter interface {
|
|
GetPeer(id *discover.NodeID) *adapters.Peer
|
|
}
|
|
|
|
type TestMessenger interface {
|
|
// MsgPipe([]byte, []byte) p2p,MsgPipe
|
|
ExpectMsg(p2p.MsgReader, uint64, interface{}) error
|
|
TriggerMsg(p2p.MsgWriter, uint64, interface{}) error
|
|
}
|
|
|
|
// exchanges are the basic units of protocol tests
|
|
// an exchange is defined on a session
|
|
type Exchange struct {
|
|
Triggers []Trigger
|
|
Expects []Expect
|
|
}
|
|
|
|
// part of the exchange, incoming message from a set of peers
|
|
type Trigger struct {
|
|
Msg interface{} // type of message to be sent
|
|
Code uint64 // code of message is given
|
|
Peer *discover.NodeID // the peer to send the message to
|
|
Timeout time.Duration // timeout duration for the sending
|
|
}
|
|
|
|
type Expect struct {
|
|
Msg interface{} // type of message to expect
|
|
Code uint64 // code of message is now given
|
|
Peer *discover.NodeID // the peer that expects the message
|
|
Timeout time.Duration // timeout duration for receiving
|
|
}
|
|
|
|
type Disconnect struct {
|
|
Peer *discover.NodeID // the peer that expects the message
|
|
Error error
|
|
}
|
|
|
|
// NewExchangeTestSession takes a network session and Messenger
|
|
// and returns an exchange session test driver that can
|
|
// be used to unit test protocol communications
|
|
// it allows for resource-driven scenario testing
|
|
// disconnect reason errors are written in session.Errs
|
|
// (correcponding to session.Peers)
|
|
func NewExchangeTestSession(t *testing.T, n TestNetAdapter, m TestMessenger, ids []*discover.NodeID) *ExchangeTestSession {
|
|
return &ExchangeTestSession{
|
|
IDs: ids,
|
|
TestNetAdapter: n,
|
|
TestMessenger: m,
|
|
t: t,
|
|
}
|
|
}
|
|
|
|
type TestPeerInfo struct {
|
|
RW p2p.MsgReadWriter
|
|
Flushc chan bool
|
|
Errc chan error
|
|
}
|
|
|
|
// trigger sends messages from peers
|
|
func (self *ExchangeTestSession) trigger(trig Trigger) error {
|
|
peer := self.GetPeer(trig.Peer)
|
|
if peer == nil {
|
|
panic(fmt.Sprintf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.IDs)))
|
|
}
|
|
rw := peer.RW
|
|
if rw == nil {
|
|
return fmt.Errorf("trigger: peer %v unreachable", trig.Peer)
|
|
}
|
|
errc := make(chan error)
|
|
|
|
go func() {
|
|
glog.V(6).Infof("trigger....")
|
|
errc <- self.TriggerMsg(rw, trig.Code, trig.Msg)
|
|
glog.V(6).Infof("triggered")
|
|
}()
|
|
|
|
t := trig.Timeout
|
|
if t == time.Duration(0) {
|
|
t = 1000 * time.Millisecond
|
|
}
|
|
alarm := time.NewTimer(t)
|
|
select {
|
|
case err := <-errc:
|
|
return err
|
|
case <-alarm.C:
|
|
return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
|
|
}
|
|
}
|
|
|
|
func Key(id []byte) string {
|
|
return string(id)
|
|
}
|
|
|
|
// expect checks an expectation
|
|
func (self *ExchangeTestSession) expect(exp Expect) error {
|
|
if exp.Msg == nil {
|
|
panic("no message to expect")
|
|
}
|
|
peer := self.GetPeer(exp.Peer)
|
|
if peer == nil {
|
|
panic(fmt.Sprintf("expect: peer %v does not exist (1- %v)", exp.Peer, len(self.IDs)))
|
|
}
|
|
rw := peer.RW
|
|
if rw == nil {
|
|
return fmt.Errorf("trigger: peer %v unreachable", exp.Peer)
|
|
}
|
|
|
|
errc := make(chan error)
|
|
go func() {
|
|
glog.V(6).Infof("waiting for msg, %v", exp.Msg)
|
|
errc <- self.ExpectMsg(rw, exp.Code, exp.Msg)
|
|
}()
|
|
|
|
t := exp.Timeout
|
|
if t == time.Duration(0) {
|
|
t = 1000 * time.Millisecond
|
|
}
|
|
alarm := time.NewTimer(t)
|
|
select {
|
|
case err := <-errc:
|
|
glog.V(6).Infof("expected msg arrives with error %v", err)
|
|
return err
|
|
case <-alarm.C:
|
|
glog.V(6).Infof("caught timeout")
|
|
return fmt.Errorf("timout expecting %v sent to peer %v", exp.Msg, exp.Peer)
|
|
}
|
|
// fatal upon encountering first exchange error
|
|
}
|
|
|
|
// TestExchange tests a series of exchanges againsts the session
|
|
func (self *ExchangeTestSession) TestExchanges(exchanges ...Exchange) {
|
|
// launch all triggers of this exchanges
|
|
|
|
for i, e := range exchanges {
|
|
errc := make(chan error)
|
|
wg := &sync.WaitGroup{}
|
|
for _, trig := range e.Triggers {
|
|
wg.Add(1)
|
|
// separate go routing to allow parallel requests
|
|
go func(t Trigger) {
|
|
defer wg.Done()
|
|
err := self.trigger(t)
|
|
if err != nil {
|
|
errc <- err
|
|
}
|
|
}(trig)
|
|
}
|
|
|
|
// each expectation is spawned in separate go-routine
|
|
// expectations of an exchange are conjunctive but uordered, i.e., only all of them arriving constitutes a pass
|
|
// each expectation is meant to be for a different peer, otherwise they are expected to panic
|
|
// testing of an exchange blocks until all expectations are decided
|
|
// an expectation is decided if
|
|
// expected message arrives OR
|
|
// an unexpected message arrives (panic)
|
|
// times out on their individual tiemeout
|
|
for _, ex := range e.Expects {
|
|
wg.Add(1)
|
|
// expect msg spawned to separate go routine
|
|
go func(exp Expect) {
|
|
defer wg.Done()
|
|
err := self.expect(exp)
|
|
if err != nil {
|
|
glog.V(6).Infof("expect msg fails %v", err)
|
|
errc <- err
|
|
}
|
|
}(ex)
|
|
}
|
|
|
|
// wait for all expectations
|
|
go func() {
|
|
wg.Wait()
|
|
close(errc)
|
|
}()
|
|
|
|
// time out globally or finish when all expectations satisfied
|
|
alarm := time.NewTimer(1000 * time.Millisecond)
|
|
select {
|
|
|
|
case err := <-errc:
|
|
if err != nil {
|
|
self.t.Fatalf("exchange failed with: %v", err)
|
|
} else {
|
|
glog.V(6).Infof("exchange %v run successfully", i)
|
|
}
|
|
case <-alarm.C:
|
|
self.t.Fatalf("exchange timed out")
|
|
}
|
|
}
|
|
}
|
|
|
|
type flushMsg struct{}
|
|
|
|
func flushExchange(c int, ids ...*discover.NodeID) Exchange {
|
|
var triggers []Trigger
|
|
for _, id := range ids {
|
|
triggers = append(triggers,
|
|
Trigger{
|
|
Code: uint64(c),
|
|
Msg: &flushMsg{},
|
|
Peer: id,
|
|
})
|
|
}
|
|
return Exchange{
|
|
Triggers: triggers,
|
|
}
|
|
}
|
|
|
|
var FlushMsg = &flushMsg{}
|
|
|
|
func (self *ExchangeTestSession) TestConnected(flush bool, peers ...*discover.NodeID) {
|
|
timeout := time.NewTimer(1000 * time.Millisecond)
|
|
var flushc chan bool
|
|
if !flush {
|
|
flushc = make(chan bool)
|
|
close(flushc)
|
|
}
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(len(peers))
|
|
for _, id := range peers {
|
|
glog.V(6).Infof("checking if peer %v is connected", id)
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
go func(p *discover.NodeID) {
|
|
defer wg.Done()
|
|
for {
|
|
peer := self.GetPeer(p)
|
|
if peer != nil {
|
|
if flush {
|
|
flushc = peer.Flushc
|
|
}
|
|
glog.V(6).Infof("checking if peer %v is connected", id)
|
|
select {
|
|
case <-timeout.C:
|
|
self.t.Fatalf("exchange timed out waiting for peer %v to flush", p)
|
|
case err := <-peer.Errc:
|
|
self.t.Fatalf("peer %v disconnected with error %v", p, err)
|
|
case <-flushc:
|
|
glog.V(6).Infof("peer %v is connected", p)
|
|
return
|
|
}
|
|
}
|
|
select {
|
|
case <-ticker.C:
|
|
glog.V(6).Infof("waiting for %v to connect", p)
|
|
case <-timeout.C:
|
|
self.t.Fatalf("exchange timed out waiting for peer %v to connect", p)
|
|
}
|
|
}
|
|
}(id)
|
|
}
|
|
wg.Wait()
|
|
glog.V(6).Infof("checking complete")
|
|
|
|
}
|
|
|
|
func (self *ExchangeTestSession) TestDisconnected(disconnects ...*Disconnect) {
|
|
for _, disconnect := range disconnects {
|
|
id := disconnect.Peer
|
|
err := disconnect.Error
|
|
errc := self.GetPeer(id).Errc
|
|
alarm := time.NewTimer(1000 * time.Millisecond)
|
|
select {
|
|
case derr := <-errc:
|
|
if !((err == nil && derr == nil) || err != nil && derr != nil && err.Error() == derr.Error()) {
|
|
self.t.Fatalf("unexpected error on peer %v: '%v', wanted '%v'", id, derr, err)
|
|
}
|
|
case <-alarm.C:
|
|
self.t.Fatalf("exchange timed out waiting for peer %v to disconnect", id)
|
|
}
|
|
}
|
|
}
|