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