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