github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/testing/protocolsession.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package testing 19 20 import ( 21 "errors" 22 "fmt" 23 "sync" 24 "time" 25 26 "github.com/AigarNetwork/aigar/log" 27 "github.com/AigarNetwork/aigar/p2p" 28 "github.com/AigarNetwork/aigar/p2p/enode" 29 "github.com/AigarNetwork/aigar/p2p/simulations/adapters" 30 ) 31 32 var errTimedOut = errors.New("timed out") 33 34 // ProtocolSession is a quasi simulation of a pivot node running 35 // a service and a number of dummy peers that can send (trigger) or 36 // receive (expect) messages 37 type ProtocolSession struct { 38 Server *p2p.Server 39 Nodes []*enode.Node 40 adapter *adapters.SimAdapter 41 events chan *p2p.PeerEvent 42 } 43 44 // Exchange is the basic units of protocol tests 45 // the triggers and expects in the arrays are run immediately and asynchronously 46 // thus one cannot have multiple expects for the SAME peer with DIFFERENT message types 47 // because it's unpredictable which expect will receive which message 48 // (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code) 49 // an exchange is defined on a session 50 type Exchange struct { 51 Label string 52 Triggers []Trigger 53 Expects []Expect 54 Timeout time.Duration 55 } 56 57 // Trigger is part of the exchange, incoming message for the pivot node 58 // sent by a peer 59 type Trigger struct { 60 Msg interface{} // type of message to be sent 61 Code uint64 // code of message is given 62 Peer enode.ID // the peer to send the message to 63 Timeout time.Duration // timeout duration for the sending 64 } 65 66 // Expect is part of an exchange, outgoing message from the pivot node 67 // received by a peer 68 type Expect struct { 69 Msg interface{} // type of message to expect 70 Code uint64 // code of message is now given 71 Peer enode.ID // the peer that expects the message 72 Timeout time.Duration // timeout duration for receiving 73 } 74 75 // Disconnect represents a disconnect event, used and checked by TestDisconnected 76 type Disconnect struct { 77 Peer enode.ID // discconnected peer 78 Error error // disconnect reason 79 } 80 81 // trigger sends messages from peers 82 func (s *ProtocolSession) trigger(trig Trigger) error { 83 simNode, ok := s.adapter.GetNode(trig.Peer) 84 if !ok { 85 return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes)) 86 } 87 mockNode, ok := simNode.Services()[0].(*mockNode) 88 if !ok { 89 return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer) 90 } 91 92 errc := make(chan error) 93 94 go func() { 95 log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code)) 96 errc <- mockNode.Trigger(&trig) 97 log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code)) 98 }() 99 100 t := trig.Timeout 101 if t == time.Duration(0) { 102 t = 1000 * time.Millisecond 103 } 104 select { 105 case err := <-errc: 106 return err 107 case <-time.After(t): 108 return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer) 109 } 110 } 111 112 // expect checks an expectation of a message sent out by the pivot node 113 func (s *ProtocolSession) expect(exps []Expect) error { 114 // construct a map of expectations for each node 115 peerExpects := make(map[enode.ID][]Expect) 116 for _, exp := range exps { 117 if exp.Msg == nil { 118 return errors.New("no message to expect") 119 } 120 peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp) 121 } 122 123 // construct a map of mockNodes for each node 124 mockNodes := make(map[enode.ID]*mockNode) 125 for nodeID := range peerExpects { 126 simNode, ok := s.adapter.GetNode(nodeID) 127 if !ok { 128 return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes)) 129 } 130 mockNode, ok := simNode.Services()[0].(*mockNode) 131 if !ok { 132 return fmt.Errorf("trigger: peer %v is not a mock", nodeID) 133 } 134 mockNodes[nodeID] = mockNode 135 } 136 137 // done chanell cancels all created goroutines when function returns 138 done := make(chan struct{}) 139 defer close(done) 140 // errc catches the first error from 141 errc := make(chan error) 142 143 wg := &sync.WaitGroup{} 144 wg.Add(len(mockNodes)) 145 for nodeID, mockNode := range mockNodes { 146 nodeID := nodeID 147 mockNode := mockNode 148 go func() { 149 defer wg.Done() 150 151 // Sum all Expect timeouts to give the maximum 152 // time for all expectations to finish. 153 // mockNode.Expect checks all received messages against 154 // a list of expected messages and timeout for each 155 // of them can not be checked separately. 156 var t time.Duration 157 for _, exp := range peerExpects[nodeID] { 158 if exp.Timeout == time.Duration(0) { 159 t += 2000 * time.Millisecond 160 } else { 161 t += exp.Timeout 162 } 163 } 164 alarm := time.NewTimer(t) 165 defer alarm.Stop() 166 167 // expectErrc is used to check if error returned 168 // from mockNode.Expect is not nil and to send it to 169 // errc only in that case. 170 // done channel will be closed when function 171 expectErrc := make(chan error) 172 go func() { 173 select { 174 case expectErrc <- mockNode.Expect(peerExpects[nodeID]...): 175 case <-done: 176 case <-alarm.C: 177 } 178 }() 179 180 select { 181 case err := <-expectErrc: 182 if err != nil { 183 select { 184 case errc <- err: 185 case <-done: 186 case <-alarm.C: 187 errc <- errTimedOut 188 } 189 } 190 case <-done: 191 case <-alarm.C: 192 errc <- errTimedOut 193 } 194 195 }() 196 } 197 198 go func() { 199 wg.Wait() 200 // close errc when all goroutines finish to return nill err from errc 201 close(errc) 202 }() 203 204 return <-errc 205 } 206 207 // TestExchanges tests a series of exchanges against the session 208 func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error { 209 for i, e := range exchanges { 210 if err := s.testExchange(e); err != nil { 211 return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err) 212 } 213 log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label)) 214 } 215 return nil 216 } 217 218 // testExchange tests a single Exchange. 219 // Default timeout value is 2 seconds. 220 func (s *ProtocolSession) testExchange(e Exchange) error { 221 errc := make(chan error) 222 done := make(chan struct{}) 223 defer close(done) 224 225 go func() { 226 for _, trig := range e.Triggers { 227 err := s.trigger(trig) 228 if err != nil { 229 errc <- err 230 return 231 } 232 } 233 234 select { 235 case errc <- s.expect(e.Expects): 236 case <-done: 237 } 238 }() 239 240 // time out globally or finish when all expectations satisfied 241 t := e.Timeout 242 if t == 0 { 243 t = 2000 * time.Millisecond 244 } 245 alarm := time.NewTimer(t) 246 select { 247 case err := <-errc: 248 return err 249 case <-alarm.C: 250 return errTimedOut 251 } 252 } 253 254 // TestDisconnected tests the disconnections given as arguments 255 // the disconnect structs describe what disconnect error is expected on which peer 256 func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { 257 expects := make(map[enode.ID]error) 258 for _, disconnect := range disconnects { 259 expects[disconnect.Peer] = disconnect.Error 260 } 261 262 timeout := time.After(time.Second) 263 for len(expects) > 0 { 264 select { 265 case event := <-s.events: 266 if event.Type != p2p.PeerEventTypeDrop { 267 continue 268 } 269 expectErr, ok := expects[event.Peer] 270 if !ok { 271 continue 272 } 273 274 if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) { 275 return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error) 276 } 277 delete(expects, event.Peer) 278 case <-timeout: 279 return fmt.Errorf("timed out waiting for peers to disconnect") 280 } 281 } 282 return nil 283 }