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