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