github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/transaction/deferred_block_persist_test.go (about) 1 package transaction_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/dgraph-io/badger/v2" 8 "github.com/stretchr/testify/require" 9 "github.com/stretchr/testify/suite" 10 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/storage/badger/transaction" 13 "github.com/onflow/flow-go/utils/unittest" 14 ) 15 16 func TestDeferredBlockPersist(t *testing.T) { 17 suite.Run(t, new(DeferredBlockPersistSuite)) 18 } 19 20 type DeferredBlockPersistSuite struct { 21 suite.Suite 22 } 23 24 // TestEmpty verifies that DeferredBlockPersist behaves like a no-op if nothing is scheduled 25 func (s *DeferredBlockPersistSuite) TestEmpty() { 26 deferredPersistOps := transaction.NewDeferredBlockPersist() 27 require.True(s.T(), deferredPersistOps.IsEmpty()) 28 29 // NewDeferredBlockPersist.Pending() should be a no-op and therefore not care that transaction.Tx is nil 30 err := deferredPersistOps.Pending()(unittest.IdentifierFixture(), nil) 31 require.NoError(s.T(), err) 32 } 33 34 // Test_AddBadgerOp adds 1 or 2 DeferredBadgerUpdate(s) and verifies that they are executed in the expected order 35 func (s *DeferredBlockPersistSuite) Test_AddBadgerOp() { 36 blockID := unittest.IdentifierFixture() 37 unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) { 38 s.Run("single DeferredBadgerUpdate", func() { 39 m := NewBlockPersistCallMonitor(s.T()) 40 deferredPersistOps := transaction.NewDeferredBlockPersist().AddBadgerOp(m.MakeBadgerUpdate()) 41 require.False(s.T(), deferredPersistOps.IsEmpty()) 42 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 43 require.NoError(s.T(), err) 44 }) 45 46 s.Run("two DeferredBadgerUpdates added individually", func() { 47 m := NewBlockPersistCallMonitor(s.T()) 48 deferredPersistOps := transaction.NewDeferredBlockPersist(). 49 AddBadgerOp(m.MakeBadgerUpdate()). 50 AddBadgerOp(m.MakeBadgerUpdate()) 51 require.False(s.T(), deferredPersistOps.IsEmpty()) 52 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 53 require.NoError(s.T(), err) 54 }) 55 56 s.Run("two DeferredBadgerUpdates added as a sequence", func() { 57 m := NewBlockPersistCallMonitor(s.T()) 58 deferredPersistOps := transaction.NewDeferredBlockPersist().AddBadgerOps( 59 m.MakeBadgerUpdate(), 60 m.MakeBadgerUpdate()) 61 require.False(s.T(), deferredPersistOps.IsEmpty()) 62 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 63 require.NoError(s.T(), err) 64 }) 65 }) 66 } 67 68 // TestDbOp adds 1 or 2 DeferredDBUpdate(s) and verifies that they are executed in the expected order 69 func (s *DeferredBlockPersistSuite) Test_AddDbOp() { 70 blockID := unittest.IdentifierFixture() 71 unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) { 72 s.Run("single DeferredDBUpdate without callback", func() { 73 m := NewBlockPersistCallMonitor(s.T()) 74 deferredPersistOps := transaction.NewDeferredBlockPersist(). 75 AddDbOp(m.MakeDBUpdate(0)) 76 require.False(s.T(), deferredPersistOps.IsEmpty()) 77 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 78 require.NoError(s.T(), err) 79 }) 80 81 s.Run("single DeferredDBUpdate with one callback", func() { 82 m := NewBlockPersistCallMonitor(s.T()) 83 deferredPersistOps := transaction.NewDeferredBlockPersist(). 84 AddDbOp(m.MakeDBUpdate(1)) 85 require.False(s.T(), deferredPersistOps.IsEmpty()) 86 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 87 require.NoError(s.T(), err) 88 }) 89 90 s.Run("single DeferredDBUpdate with multiple callbacks", func() { 91 m := NewBlockPersistCallMonitor(s.T()) 92 deferredPersistOps := transaction.NewDeferredBlockPersist(). 93 AddDbOp(m.MakeDBUpdate(21)) 94 require.False(s.T(), deferredPersistOps.IsEmpty()) 95 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 96 require.NoError(s.T(), err) 97 }) 98 99 s.Run("two DeferredDBUpdates added individually", func() { 100 m := NewBlockPersistCallMonitor(s.T()) 101 deferredPersistOps := transaction.NewDeferredBlockPersist(). 102 AddDbOp(m.MakeDBUpdate(17)). 103 AddDbOp(m.MakeDBUpdate(0)) 104 require.False(s.T(), deferredPersistOps.IsEmpty()) 105 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 106 require.NoError(s.T(), err) 107 }) 108 109 s.Run("two DeferredDBUpdates added as a sequence", func() { 110 m := NewBlockPersistCallMonitor(s.T()) 111 deferredPersistOps := transaction.NewDeferredBlockPersist() 112 deferredPersistOps.AddDbOps( 113 m.MakeDBUpdate(0), 114 m.MakeDBUpdate(17)) 115 require.False(s.T(), deferredPersistOps.IsEmpty()) 116 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 117 require.NoError(s.T(), err) 118 }) 119 }) 120 } 121 122 // Test_AddIndexingOp adds 1 or 2 DeferredBlockPersistOp(s) and verifies that they are executed in the expected order 123 func (s *DeferredBlockPersistSuite) Test_AddIndexingOp() { 124 blockID := unittest.IdentifierFixture() 125 unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) { 126 s.Run("single DeferredBlockPersistOp without callback", func() { 127 m := NewBlockPersistCallMonitor(s.T()) 128 deferredPersistOps := transaction.NewDeferredBlockPersist(). 129 AddIndexingOp(m.MakeIndexingOp(blockID, 0)) 130 require.False(s.T(), deferredPersistOps.IsEmpty()) 131 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 132 require.NoError(s.T(), err) 133 }) 134 135 s.Run("single DeferredBlockPersistOp with one callback", func() { 136 m := NewBlockPersistCallMonitor(s.T()) 137 deferredPersistOps := transaction.NewDeferredBlockPersist(). 138 AddIndexingOp(m.MakeIndexingOp(blockID, 1)) 139 require.False(s.T(), deferredPersistOps.IsEmpty()) 140 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 141 require.NoError(s.T(), err) 142 }) 143 144 s.Run("single DeferredBlockPersistOp with multiple callbacks", func() { 145 m := NewBlockPersistCallMonitor(s.T()) 146 deferredPersistOps := transaction.NewDeferredBlockPersist(). 147 AddIndexingOp(m.MakeIndexingOp(blockID, 21)) 148 require.False(s.T(), deferredPersistOps.IsEmpty()) 149 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 150 require.NoError(s.T(), err) 151 }) 152 153 s.Run("two DeferredBlockPersistOp added individually", func() { 154 m := NewBlockPersistCallMonitor(s.T()) 155 deferredPersistOps := transaction.NewDeferredBlockPersist(). 156 AddIndexingOp(m.MakeIndexingOp(blockID, 17)). 157 AddIndexingOp(m.MakeIndexingOp(blockID, 0)) 158 require.False(s.T(), deferredPersistOps.IsEmpty()) 159 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 160 require.NoError(s.T(), err) 161 }) 162 163 s.Run("two DeferredBlockPersistOp added as a sequence", func() { 164 m := NewBlockPersistCallMonitor(s.T()) 165 deferredPersistOps := transaction.NewDeferredBlockPersist() 166 deferredPersistOps.AddIndexingOps( 167 m.MakeIndexingOp(blockID, 0), 168 m.MakeIndexingOp(blockID, 17)) 169 require.False(s.T(), deferredPersistOps.IsEmpty()) 170 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 171 require.NoError(s.T(), err) 172 }) 173 }) 174 } 175 176 // Test_AddOnSucceedCallback adds 1 or 2 callback(s) and verifies that they are executed in the expected order 177 func (s *DeferredBlockPersistSuite) Test_AddOnSucceedCallback() { 178 blockID := unittest.IdentifierFixture() 179 unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) { 180 s.Run("single callback", func() { 181 m := NewBlockPersistCallMonitor(s.T()) 182 deferredPersistOps := transaction.NewDeferredBlockPersist(). 183 OnSucceed(m.MakeCallback()) 184 require.False(s.T(), deferredPersistOps.IsEmpty()) 185 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 186 require.NoError(s.T(), err) 187 }) 188 189 s.Run("two callbacks added individually", func() { 190 m := NewBlockPersistCallMonitor(s.T()) 191 deferredPersistOps := transaction.NewDeferredBlockPersist(). 192 OnSucceed(m.MakeCallback()). 193 OnSucceed(m.MakeCallback()) 194 require.False(s.T(), deferredPersistOps.IsEmpty()) 195 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 196 require.NoError(s.T(), err) 197 }) 198 199 s.Run("many callbacks added as a sequence", func() { 200 m := NewBlockPersistCallMonitor(s.T()) 201 deferredPersistOps := transaction.NewDeferredBlockPersist(). 202 OnSucceeds(m.MakeCallbacks(11)...) 203 require.False(s.T(), deferredPersistOps.IsEmpty()) 204 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 205 require.NoError(s.T(), err) 206 }) 207 }) 208 } 209 210 // Test_EverythingMixed uses all ways to add functors in combination and verifies that they are executed in the expected order 211 func (s *DeferredBlockPersistSuite) Test_EverythingMixed() { 212 blockID := unittest.IdentifierFixture() 213 unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) { 214 m := NewBlockPersistCallMonitor(s.T()) 215 deferredPersistOps := transaction.NewDeferredBlockPersist(). 216 OnSucceed(m.MakeCallback()). 217 AddDbOp(m.MakeDBUpdate(1)). 218 AddBadgerOp(m.MakeBadgerUpdate()). 219 AddIndexingOp(m.MakeIndexingOp(blockID, 2)). 220 OnSucceeds(m.MakeCallbacks(3)...). 221 AddDbOp(m.MakeDBUpdate(0)). 222 AddBadgerOps( 223 m.MakeBadgerUpdate(), 224 m.MakeBadgerUpdate(), 225 m.MakeBadgerUpdate()). 226 AddIndexingOps( 227 m.MakeIndexingOp(blockID, 7), 228 m.MakeIndexingOp(blockID, 0)). 229 OnSucceeds( 230 m.MakeCallback(), 231 m.MakeCallback()). 232 AddDbOps( 233 m.MakeDBUpdate(7), 234 m.MakeDBUpdate(0), 235 m.MakeDBUpdate(1)). 236 OnSucceed(m.MakeCallback()) 237 require.False(s.T(), deferredPersistOps.IsEmpty()) 238 err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID)) 239 require.NoError(s.T(), err) 240 }) 241 } 242 243 /* ***************************************** Testing Utility BlockPersistCallMonitor ***************************************** */ 244 245 // BlockPersistCallMonitor is a utility for testing that DeferredBlockPersist calls its input functors and callbacks 246 // in the correct order. DeferredBlockPersist is expected to proceed as follows: 247 // 248 // 0. Record functors added via `AddBadgerOp`, `AddDbOp`, `AddIndexingOp`, `OnSucceed` ... 249 // 1. Execute the functors in the order they were added 250 // 2. During each functor's execution: 251 // - some functor's may schedule callbacks (depending on their type) 252 // - record those callbacks in the order they are scheduled (no execution yet) 253 // `OnSucceed` schedules its callback during its execution at this step as well 254 // 3. If and only if the underlying database transaction _successfully_ completed, run the callbacks 255 // 256 // To verify the correct order of calls, the BlockPersistCallMonitor generates functors. Each functor has a 257 // dedicated index value. When the functor is called, it checks that its index matches the functor index 258 // that the BlockPersistCallMonitor expects to be executed next. For callbacks, we proceed analogously. 259 // 260 // Usage note: 261 // The call BlockPersistCallMonitor assumes that functors are added to DeferredBlockPersist exactly in the order that 262 // BlockPersistCallMonitor generates them. This works very intuitively, when the tests proceed as in the following example: 263 // 264 // m := NewBlockPersistCallMonitor(t) 265 // deferredPersistOps := transaction.NewDeferredBlockPersist() 266 // deferredPersistOps.AddBadgerOp(m.MakeBadgerUpdate()) // here, we add the functor right when it is generated 267 // transaction.Update(db, deferredPersistOps.Pending()) 268 type BlockPersistCallMonitor struct { 269 generatedTxFunctors int 270 generatedCallbacks int 271 272 T *testing.T 273 nextExpectedTxFunctorIdx int 274 nextExpectedCallbackIdx int 275 } 276 277 func NewBlockPersistCallMonitor(t *testing.T) *BlockPersistCallMonitor { 278 return &BlockPersistCallMonitor{T: t} 279 } 280 281 func (cm *BlockPersistCallMonitor) MakeIndexingOp(expectedBlockID flow.Identifier, withCallbacks int) transaction.DeferredBlockPersistOp { 282 myFunctorIdx := cm.generatedTxFunctors // copy into local scope. Determined when we construct functor 283 callbacks := cm.MakeCallbacks(withCallbacks) // pre-generate callback functors 284 functor := func(blockID flow.Identifier, tx *transaction.Tx) error { 285 if expectedBlockID != blockID { 286 cm.T.Errorf("expected block ID %v but got %v", expectedBlockID, blockID) 287 return fmt.Errorf("expected block ID %v but got %v", expectedBlockID, blockID) 288 } 289 for _, c := range callbacks { 290 tx.OnSucceed(c) // schedule callback 291 } 292 if cm.nextExpectedTxFunctorIdx != myFunctorIdx { 293 // nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist 294 // should execute the functors in the order they were added, which is violated. Hence, we fail: 295 cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 296 return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 297 } 298 299 // happy path: 300 cm.nextExpectedTxFunctorIdx += 1 301 return nil 302 } 303 304 cm.generatedTxFunctors += 1 305 return functor 306 } 307 308 func (cm *BlockPersistCallMonitor) MakeDBUpdate(withCallbacks int) transaction.DeferredDBUpdate { 309 myFunctorIdx := cm.generatedTxFunctors // copy into local scope. Determined when we construct functor 310 callbacks := cm.MakeCallbacks(withCallbacks) // pre-generate callback functors 311 functor := func(tx *transaction.Tx) error { 312 for _, c := range callbacks { 313 tx.OnSucceed(c) // schedule callback 314 } 315 if cm.nextExpectedTxFunctorIdx != myFunctorIdx { 316 // nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist 317 // should execute the functors in the order they were added, which is violated. Hence, we fail: 318 cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 319 return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 320 } 321 322 // happy path: 323 cm.nextExpectedTxFunctorIdx += 1 324 return nil 325 } 326 327 cm.generatedTxFunctors += 1 328 return functor 329 } 330 331 func (cm *BlockPersistCallMonitor) MakeBadgerUpdate() transaction.DeferredBadgerUpdate { 332 myFunctorIdx := cm.generatedTxFunctors // copy into local scope. Determined when we construct functor 333 functor := func(tx *badger.Txn) error { 334 if cm.nextExpectedTxFunctorIdx != myFunctorIdx { 335 // nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist 336 // should execute the functors in the order they were added, which is violated. Hence, we fail: 337 cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 338 return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx) 339 } 340 341 // happy path: 342 cm.nextExpectedTxFunctorIdx += 1 343 return nil 344 } 345 346 cm.generatedTxFunctors += 1 347 return functor 348 } 349 350 func (cm *BlockPersistCallMonitor) MakeCallback() func() { 351 myFunctorIdx := cm.generatedCallbacks // copy into local scope. Determined when we construct callback 352 functor := func() { 353 if cm.nextExpectedCallbackIdx != myFunctorIdx { 354 // nextExpectedCallbackIdx holds the Index of the callback that was generated next. DeferredBlockPersist 355 // should execute the callback in the order they were scheduled, which is violated. Hence, we fail: 356 cm.T.Errorf("expected next Callback Index is %d but my value is %d", cm.nextExpectedCallbackIdx, myFunctorIdx) 357 } 358 cm.nextExpectedCallbackIdx += 1 // happy path 359 } 360 361 cm.generatedCallbacks += 1 362 return functor 363 } 364 365 func (cm *BlockPersistCallMonitor) MakeCallbacks(numberCallbacks int) []func() { 366 callbacks := make([]func(), 0, numberCallbacks) 367 for ; 0 < numberCallbacks; numberCallbacks-- { 368 callbacks = append(callbacks, cm.MakeCallback()) 369 } 370 return callbacks 371 }