protocoltester.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. /*
  17. the p2p/testing package provides a unit test scheme to check simple
  18. protocol message exchanges with one pivot node and a number of dummy peers
  19. The pivot test node runs a node.Service, the dummy peers run a mock node
  20. that can be used to send and receive messages
  21. */
  22. package testing
  23. import (
  24. "bytes"
  25. "fmt"
  26. "io"
  27. "io/ioutil"
  28. "strings"
  29. "sync"
  30. "testing"
  31. "github.com/ethereum/go-ethereum/log"
  32. "github.com/ethereum/go-ethereum/node"
  33. "github.com/ethereum/go-ethereum/p2p"
  34. "github.com/ethereum/go-ethereum/p2p/discover"
  35. "github.com/ethereum/go-ethereum/p2p/simulations"
  36. "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
  37. "github.com/ethereum/go-ethereum/rlp"
  38. "github.com/ethereum/go-ethereum/rpc"
  39. )
  40. // ProtocolTester is the tester environment used for unit testing protocol
  41. // message exchanges. It uses p2p/simulations framework
  42. type ProtocolTester struct {
  43. *ProtocolSession
  44. network *simulations.Network
  45. }
  46. // NewProtocolTester constructs a new ProtocolTester
  47. // it takes as argument the pivot node id, the number of dummy peers and the
  48. // protocol run function called on a peer connection by the p2p server
  49. func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
  50. services := adapters.Services{
  51. "test": func(ctx *adapters.ServiceContext) (node.Service, error) {
  52. return &testNode{run}, nil
  53. },
  54. "mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
  55. return newMockNode(), nil
  56. },
  57. }
  58. adapter := adapters.NewSimAdapter(services)
  59. net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
  60. if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{
  61. ID: id,
  62. EnableMsgEvents: true,
  63. Services: []string{"test"},
  64. }); err != nil {
  65. panic(err.Error())
  66. }
  67. if err := net.Start(id); err != nil {
  68. panic(err.Error())
  69. }
  70. node := net.GetNode(id).Node.(*adapters.SimNode)
  71. peers := make([]*adapters.NodeConfig, n)
  72. peerIDs := make([]discover.NodeID, n)
  73. for i := 0; i < n; i++ {
  74. peers[i] = adapters.RandomNodeConfig()
  75. peers[i].Services = []string{"mock"}
  76. peerIDs[i] = peers[i].ID
  77. }
  78. events := make(chan *p2p.PeerEvent, 1000)
  79. node.SubscribeEvents(events)
  80. ps := &ProtocolSession{
  81. Server: node.Server(),
  82. IDs: peerIDs,
  83. adapter: adapter,
  84. events: events,
  85. }
  86. self := &ProtocolTester{
  87. ProtocolSession: ps,
  88. network: net,
  89. }
  90. self.Connect(id, peers...)
  91. return self
  92. }
  93. // Stop stops the p2p server
  94. func (t *ProtocolTester) Stop() error {
  95. t.Server.Stop()
  96. return nil
  97. }
  98. // Connect brings up the remote peer node and connects it using the
  99. // p2p/simulations network connection with the in memory network adapter
  100. func (t *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
  101. for _, peer := range peers {
  102. log.Trace(fmt.Sprintf("start node %v", peer.ID))
  103. if _, err := t.network.NewNodeWithConfig(peer); err != nil {
  104. panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
  105. }
  106. if err := t.network.Start(peer.ID); err != nil {
  107. panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
  108. }
  109. log.Trace(fmt.Sprintf("connect to %v", peer.ID))
  110. if err := t.network.Connect(selfID, peer.ID); err != nil {
  111. panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
  112. }
  113. }
  114. }
  115. // testNode wraps a protocol run function and implements the node.Service
  116. // interface
  117. type testNode struct {
  118. run func(*p2p.Peer, p2p.MsgReadWriter) error
  119. }
  120. func (t *testNode) Protocols() []p2p.Protocol {
  121. return []p2p.Protocol{{
  122. Length: 100,
  123. Run: t.run,
  124. }}
  125. }
  126. func (t *testNode) APIs() []rpc.API {
  127. return nil
  128. }
  129. func (t *testNode) Start(server *p2p.Server) error {
  130. return nil
  131. }
  132. func (t *testNode) Stop() error {
  133. return nil
  134. }
  135. // mockNode is a testNode which doesn't actually run a protocol, instead
  136. // exposing channels so that tests can manually trigger and expect certain
  137. // messages
  138. type mockNode struct {
  139. testNode
  140. trigger chan *Trigger
  141. expect chan []Expect
  142. err chan error
  143. stop chan struct{}
  144. stopOnce sync.Once
  145. }
  146. func newMockNode() *mockNode {
  147. mock := &mockNode{
  148. trigger: make(chan *Trigger),
  149. expect: make(chan []Expect),
  150. err: make(chan error),
  151. stop: make(chan struct{}),
  152. }
  153. mock.testNode.run = mock.Run
  154. return mock
  155. }
  156. // Run is a protocol run function which just loops waiting for tests to
  157. // instruct it to either trigger or expect a message from the peer
  158. func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
  159. for {
  160. select {
  161. case trig := <-m.trigger:
  162. m.err <- p2p.Send(rw, trig.Code, trig.Msg)
  163. case exps := <-m.expect:
  164. m.err <- expectMsgs(rw, exps)
  165. case <-m.stop:
  166. return nil
  167. }
  168. }
  169. }
  170. func (m *mockNode) Trigger(trig *Trigger) error {
  171. m.trigger <- trig
  172. return <-m.err
  173. }
  174. func (m *mockNode) Expect(exp ...Expect) error {
  175. m.expect <- exp
  176. return <-m.err
  177. }
  178. func (m *mockNode) Stop() error {
  179. m.stopOnce.Do(func() { close(m.stop) })
  180. return nil
  181. }
  182. func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
  183. matched := make([]bool, len(exps))
  184. for {
  185. msg, err := rw.ReadMsg()
  186. if err != nil {
  187. if err == io.EOF {
  188. break
  189. }
  190. return err
  191. }
  192. actualContent, err := ioutil.ReadAll(msg.Payload)
  193. if err != nil {
  194. return err
  195. }
  196. var found bool
  197. for i, exp := range exps {
  198. if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(exp.Msg)) {
  199. if matched[i] {
  200. return fmt.Errorf("message #%d received two times", i)
  201. }
  202. matched[i] = true
  203. found = true
  204. break
  205. }
  206. }
  207. if !found {
  208. expected := make([]string, 0)
  209. for i, exp := range exps {
  210. if matched[i] {
  211. continue
  212. }
  213. expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(exp.Msg)))
  214. }
  215. return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
  216. }
  217. done := true
  218. for _, m := range matched {
  219. if !m {
  220. done = false
  221. break
  222. }
  223. }
  224. if done {
  225. return nil
  226. }
  227. }
  228. for i, m := range matched {
  229. if !m {
  230. return fmt.Errorf("expected message #%d not received", i)
  231. }
  232. }
  233. return nil
  234. }
  235. // mustEncodeMsg uses rlp to encode a message.
  236. // In case of error it panics.
  237. func mustEncodeMsg(msg interface{}) []byte {
  238. contentEnc, err := rlp.EncodeToBytes(msg)
  239. if err != nil {
  240. panic("content encode error: " + err.Error())
  241. }
  242. return contentEnc
  243. }