github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/integration/rpctest/tx_expecter.go (about) 1 package rpctest 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/hyperledger/burrow/binary" 11 "github.com/hyperledger/burrow/event" 12 "github.com/hyperledger/burrow/execution/exec" 13 "github.com/hyperledger/burrow/rpc/rpcevents" 14 ) 15 16 const ( 17 maximumDurationWithoutProgress = 5 * time.Second 18 subscriptionBuffer = event.DefaultEventBufferCapacity + 1 19 logCommits = false 20 ) 21 22 type TxExpecter struct { 23 sync.Mutex 24 emitter *event.Emitter 25 subID string 26 name string 27 all map[string]struct{} 28 expected map[string]struct{} 29 received map[string]struct{} 30 asserted bool 31 succeeded chan struct{} 32 ready chan struct{} 33 previousTotal int 34 blockRange *rpcevents.BlockRange 35 } 36 37 // Start listening for blocks and cross off any transactions that were expected. 38 // Expect can be called multiple times before a single call to AssertCommitted. 39 // TxExpecter is single-shot - create multiple TxExpecters if you want to call AssertCommitted multiple times. 40 func ExpectTxs(emitter *event.Emitter, name string) *TxExpecter { 41 exp := &TxExpecter{ 42 emitter: emitter, 43 subID: fmt.Sprintf("%s_%s", name, event.GenSubID()), 44 name: name, 45 all: make(map[string]struct{}), 46 expected: make(map[string]struct{}), 47 received: make(map[string]struct{}), 48 succeeded: make(chan struct{}), 49 ready: make(chan struct{}), 50 blockRange: &rpcevents.BlockRange{}, 51 } 52 go exp.listen() 53 <-exp.ready 54 return exp 55 } 56 57 // Expect a transaction to be committed 58 func (exp *TxExpecter) Expect(txHash binary.HexBytes) { 59 exp.Lock() 60 defer exp.Unlock() 61 if exp.closed() { 62 panic(fmt.Errorf("cannot call Expect after AssertCommitted")) 63 } 64 key := txHash.String() 65 exp.expected[key] = struct{}{} 66 exp.all[key] = struct{}{} 67 } 68 69 // Assert that all expected transactions are committed. Will block until all expected transactions are committed. 70 // Returns the BlockRange over which the transactions were committed. 71 func (exp *TxExpecter) AssertCommitted(t testing.TB) *rpcevents.BlockRange { 72 exp.Lock() 73 // close() clears subID to indicate this TxExpecter ha been used 74 if exp.closed() { 75 panic(fmt.Errorf("cannot call AssertCommitted more than once")) 76 } 77 exp.asserted = true 78 succeeded := exp.reconcile() 79 exp.Unlock() 80 defer exp.close() 81 if succeeded { 82 return exp.Pass() 83 } 84 var err error 85 for err == nil { 86 select { 87 case <-exp.succeeded: 88 return exp.Pass() 89 case <-time.After(maximumDurationWithoutProgress): 90 err = exp.assertMakingProgress() 91 } 92 } 93 t.Fatal(err) 94 return nil 95 } 96 97 func (exp *TxExpecter) Pass() *rpcevents.BlockRange { 98 fmt.Printf("%s: Successfully committed %d txs\n", exp.name, len(exp.all)) 99 return exp.blockRange 100 } 101 102 func (exp *TxExpecter) listen() { 103 numTxs := 0 104 ch, err := exp.emitter.Subscribe(context.Background(), exp.subID, exec.QueryForBlockExecution(), subscriptionBuffer) 105 if err != nil { 106 panic(fmt.Errorf("ExpectTxs(): could not subscribe to blocks: %v", err)) 107 } 108 close(exp.ready) 109 for msg := range ch { 110 be := msg.(*exec.BlockExecution) 111 blockTxs := len(be.TxExecutions) 112 numTxs += blockTxs 113 if logCommits { 114 fmt.Printf("%s: Total TXs committed at block %v: %v (+%v)\n", exp.name, be.GetHeight(), numTxs, blockTxs) 115 } 116 for _, txe := range be.TxExecutions { 117 // Return if this is the last expected transaction (and we are finished expecting) 118 if exp.receive(txe) { 119 close(exp.succeeded) 120 return 121 } 122 } 123 } 124 } 125 126 func (exp *TxExpecter) close() { 127 exp.Lock() 128 defer exp.Unlock() 129 err := exp.emitter.UnsubscribeAll(context.Background(), exp.subID) 130 if err != nil { 131 fmt.Printf("TxExpecter could not unsubscribe: %v\n", err) 132 } 133 exp.subID = "" 134 } 135 136 func (exp *TxExpecter) closed() bool { 137 return exp.subID == "" 138 } 139 140 func (exp *TxExpecter) receive(txe *exec.TxExecution) (done bool) { 141 exp.Lock() 142 defer exp.Unlock() 143 exp.received[txe.TxHash.String()] = struct{}{} 144 if exp.blockRange.Start == nil { 145 exp.blockRange.Start = rpcevents.AbsoluteBound(txe.Height) 146 exp.blockRange.End = rpcevents.AbsoluteBound(txe.Height) 147 } 148 exp.blockRange.End.Index = txe.Height 149 if exp.asserted { 150 return exp.reconcile() 151 } 152 return false 153 } 154 155 func (exp *TxExpecter) reconcile() (done bool) { 156 for re := range exp.received { 157 if _, ok := exp.expected[re]; ok { 158 // Remove from expected 159 delete(exp.expected, re) 160 // No longer need to cache in received 161 delete(exp.received, re) 162 } 163 } 164 total := len(exp.expected) 165 return total == 0 166 } 167 168 func (exp *TxExpecter) assertMakingProgress() error { 169 exp.Lock() 170 defer exp.Unlock() 171 total := len(exp.expected) 172 if exp.previousTotal == 0 { 173 exp.previousTotal = total 174 return nil 175 } 176 // if the total is reducing we are making progress 177 if total < exp.previousTotal { 178 return nil 179 } 180 committed := len(exp.all) - total 181 committedString := "none" 182 if committed != 0 { 183 committedString = fmt.Sprintf("only %d", committed) 184 } 185 return fmt.Errorf("TxExpecter timed out after %v: expecting %d txs to be committed but %s were", 186 maximumDurationWithoutProgress, total, committedString) 187 }