protocoltester.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. "github.com/ethereum/go-ethereum/log"
  31. "github.com/ethereum/go-ethereum/node"
  32. "github.com/ethereum/go-ethereum/p2p"
  33. "github.com/ethereum/go-ethereum/p2p/enode"
  34. "github.com/ethereum/go-ethereum/p2p/simulations"
  35. "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
  36. "github.com/ethereum/go-ethereum/rlp"
  37. "github.com/ethereum/go-ethereum/rpc"
  38. )
  39. // ProtocolTester is the tester environment used for unit testing protocol
  40. // message exchanges. It uses p2p/simulations framework
  41. type ProtocolTester struct {
  42. *ProtocolSession
  43. network *simulations.Network
  44. }
  45. // NewProtocolTester constructs a new ProtocolTester
  46. // it takes as argument the pivot node id, the number of dummy peers and the
  47. // protocol run function called on a peer connection by the p2p server
  48. func NewProtocolTester(id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
  49. services := adapters.Services{
  50. "test": func(ctx *adapters.ServiceContext) (node.Service, error) {
  51. return &testNode{run}, nil
  52. },
  53. "mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
  54. return newMockNode(), nil
  55. },
  56. }
  57. adapter := adapters.NewSimAdapter(services)
  58. net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
  59. if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{
  60. ID: id,
  61. EnableMsgEvents: true,
  62. Services: []string{"test"},
  63. }); err != nil {
  64. panic(err.Error())
  65. }
  66. if err := net.Start(id); err != nil {
  67. panic(err.Error())
  68. }
  69. node := net.GetNode(id).Node.(*adapters.SimNode)
  70. peers := make([]*adapters.NodeConfig, n)
  71. nodes := make([]*enode.Node, n)
  72. for i := 0; i < n; i++ {
  73. peers[i] = adapters.RandomNodeConfig()
  74. peers[i].Services = []string{"mock"}
  75. nodes[i] = peers[i].Node()
  76. }
  77. events := make(chan *p2p.PeerEvent, 1000)
  78. node.SubscribeEvents(events)
  79. ps := &ProtocolSession{
  80. Server: node.Server(),
  81. Nodes: nodes,
  82. adapter: adapter,
  83. events: events,
  84. }
  85. self := &ProtocolTester{
  86. ProtocolSession: ps,
  87. network: net,
  88. }
  89. self.Connect(id, peers...)
  90. return self
  91. }
  92. // Stop stops the p2p server
  93. func (t *ProtocolTester) Stop() error {
  94. t.Server.Stop()
  95. return nil
  96. }
  97. // Connect brings up the remote peer node and connects it using the
  98. // p2p/simulations network connection with the in memory network adapter
  99. func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) {
  100. for _, peer := range peers {
  101. log.Trace(fmt.Sprintf("start node %v", peer.ID))
  102. if _, err := t.network.NewNodeWithConfig(peer); err != nil {
  103. panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
  104. }
  105. if err := t.network.Start(peer.ID); err != nil {
  106. panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
  107. }
  108. log.Trace(fmt.Sprintf("connect to %v", peer.ID))
  109. if err := t.network.Connect(selfID, peer.ID); err != nil {
  110. panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
  111. }
  112. }
  113. }
  114. // testNode wraps a protocol run function and implements the node.Service
  115. // interface
  116. type testNode struct {
  117. run func(*p2p.Peer, p2p.MsgReadWriter) error
  118. }
  119. func (t *testNode) Protocols() []p2p.Protocol {
  120. return []p2p.Protocol{{
  121. Length: 100,
  122. Run: t.run,
  123. }}
  124. }
  125. func (t *testNode) APIs() []rpc.API {
  126. return nil
  127. }
  128. func (t *testNode) Start(server *p2p.Server) error {
  129. return nil
  130. }
  131. func (t *testNode) Stop() error {
  132. return nil
  133. }
  134. // mockNode is a testNode which doesn't actually run a protocol, instead
  135. // exposing channels so that tests can manually trigger and expect certain
  136. // messages
  137. type mockNode struct {
  138. testNode
  139. trigger chan *Trigger
  140. expect chan []Expect
  141. err chan error
  142. stop chan struct{}
  143. stopOnce sync.Once
  144. }
  145. func newMockNode() *mockNode {
  146. mock := &mockNode{
  147. trigger: make(chan *Trigger),
  148. expect: make(chan []Expect),
  149. err: make(chan error),
  150. stop: make(chan struct{}),
  151. }
  152. mock.testNode.run = mock.Run
  153. return mock
  154. }
  155. // Run is a protocol run function which just loops waiting for tests to
  156. // instruct it to either trigger or expect a message from the peer
  157. func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
  158. for {
  159. select {
  160. case trig := <-m.trigger:
  161. wmsg := Wrap(trig.Msg)
  162. m.err <- p2p.Send(rw, trig.Code, wmsg)
  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(Wrap(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(Wrap(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. }
  244. type WrappedMsg struct {
  245. Context []byte
  246. Size uint32
  247. Payload []byte
  248. }
  249. func Wrap(msg interface{}) interface{} {
  250. data, _ := rlp.EncodeToBytes(msg)
  251. return &WrappedMsg{
  252. Size: uint32(len(data)),
  253. Payload: data,
  254. }
  255. }