github.com/lianghucheng/zrddz@v0.0.0-20200923083010-c71f680932e2/src/gopkg.in/mgo.v2/txn/txn_test.go (about) 1 package txn_test 2 3 import ( 4 "flag" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 . "gopkg.in/check.v1" 11 "gopkg.in/mgo.v2" 12 "gopkg.in/mgo.v2/bson" 13 "gopkg.in/mgo.v2/dbtest" 14 "gopkg.in/mgo.v2/txn" 15 ) 16 17 func TestAll(t *testing.T) { 18 TestingT(t) 19 } 20 21 type S struct { 22 server dbtest.DBServer 23 session *mgo.Session 24 db *mgo.Database 25 tc, sc *mgo.Collection 26 accounts *mgo.Collection 27 runner *txn.Runner 28 } 29 30 var _ = Suite(&S{}) 31 32 type M map[string]interface{} 33 34 func (s *S) SetUpSuite(c *C) { 35 s.server.SetPath(c.MkDir()) 36 } 37 38 func (s *S) TearDownSuite(c *C) { 39 s.server.Stop() 40 } 41 42 func (s *S) SetUpTest(c *C) { 43 s.server.Wipe() 44 45 txn.SetChaos(txn.Chaos{}) 46 txn.SetLogger(c) 47 txn.SetDebug(true) 48 49 s.session = s.server.Session() 50 s.db = s.session.DB("test") 51 s.tc = s.db.C("tc") 52 s.sc = s.db.C("tc.stash") 53 s.accounts = s.db.C("accounts") 54 s.runner = txn.NewRunner(s.tc) 55 } 56 57 func (s *S) TearDownTest(c *C) { 58 txn.SetLogger(nil) 59 txn.SetDebug(false) 60 s.session.Close() 61 } 62 63 type Account struct { 64 Id int `bson:"_id"` 65 Balance int 66 } 67 68 func (s *S) TestDocExists(c *C) { 69 err := s.accounts.Insert(M{"_id": 0, "balance": 300}) 70 c.Assert(err, IsNil) 71 72 exists := []txn.Op{{ 73 C: "accounts", 74 Id: 0, 75 Assert: txn.DocExists, 76 }} 77 missing := []txn.Op{{ 78 C: "accounts", 79 Id: 0, 80 Assert: txn.DocMissing, 81 }} 82 83 err = s.runner.Run(exists, "", nil) 84 c.Assert(err, IsNil) 85 err = s.runner.Run(missing, "", nil) 86 c.Assert(err, Equals, txn.ErrAborted) 87 88 err = s.accounts.RemoveId(0) 89 c.Assert(err, IsNil) 90 91 err = s.runner.Run(exists, "", nil) 92 c.Assert(err, Equals, txn.ErrAborted) 93 err = s.runner.Run(missing, "", nil) 94 c.Assert(err, IsNil) 95 } 96 97 func (s *S) TestInsert(c *C) { 98 err := s.accounts.Insert(M{"_id": 0, "balance": 300}) 99 c.Assert(err, IsNil) 100 101 ops := []txn.Op{{ 102 C: "accounts", 103 Id: 0, 104 Insert: M{"balance": 200}, 105 }} 106 107 err = s.runner.Run(ops, "", nil) 108 c.Assert(err, IsNil) 109 110 var account Account 111 err = s.accounts.FindId(0).One(&account) 112 c.Assert(err, IsNil) 113 c.Assert(account.Balance, Equals, 300) 114 115 ops[0].Id = 1 116 err = s.runner.Run(ops, "", nil) 117 c.Assert(err, IsNil) 118 119 err = s.accounts.FindId(1).One(&account) 120 c.Assert(err, IsNil) 121 c.Assert(account.Balance, Equals, 200) 122 } 123 124 func (s *S) TestInsertStructID(c *C) { 125 type id struct { 126 FirstName string 127 LastName string 128 } 129 ops := []txn.Op{{ 130 C: "accounts", 131 Id: id{FirstName: "John", LastName: "Jones"}, 132 Assert: txn.DocMissing, 133 Insert: M{"balance": 200}, 134 }, { 135 C: "accounts", 136 Id: id{FirstName: "Sally", LastName: "Smith"}, 137 Assert: txn.DocMissing, 138 Insert: M{"balance": 800}, 139 }} 140 141 err := s.runner.Run(ops, "", nil) 142 c.Assert(err, IsNil) 143 144 n, err := s.accounts.Find(nil).Count() 145 c.Assert(err, IsNil) 146 c.Assert(n, Equals, 2) 147 } 148 149 func (s *S) TestRemove(c *C) { 150 err := s.accounts.Insert(M{"_id": 0, "balance": 300}) 151 c.Assert(err, IsNil) 152 153 ops := []txn.Op{{ 154 C: "accounts", 155 Id: 0, 156 Remove: true, 157 }} 158 159 err = s.runner.Run(ops, "", nil) 160 c.Assert(err, IsNil) 161 162 err = s.accounts.FindId(0).One(nil) 163 c.Assert(err, Equals, mgo.ErrNotFound) 164 165 err = s.runner.Run(ops, "", nil) 166 c.Assert(err, IsNil) 167 } 168 169 func (s *S) TestUpdate(c *C) { 170 var err error 171 err = s.accounts.Insert(M{"_id": 0, "balance": 200}) 172 c.Assert(err, IsNil) 173 err = s.accounts.Insert(M{"_id": 1, "balance": 200}) 174 c.Assert(err, IsNil) 175 176 ops := []txn.Op{{ 177 C: "accounts", 178 Id: 0, 179 Update: M{"$inc": M{"balance": 100}}, 180 }} 181 182 err = s.runner.Run(ops, "", nil) 183 c.Assert(err, IsNil) 184 185 var account Account 186 err = s.accounts.FindId(0).One(&account) 187 c.Assert(err, IsNil) 188 c.Assert(account.Balance, Equals, 300) 189 190 ops[0].Id = 1 191 192 err = s.accounts.FindId(1).One(&account) 193 c.Assert(err, IsNil) 194 c.Assert(account.Balance, Equals, 200) 195 } 196 197 func (s *S) TestInsertUpdate(c *C) { 198 ops := []txn.Op{{ 199 C: "accounts", 200 Id: 0, 201 Insert: M{"_id": 0, "balance": 200}, 202 }, { 203 C: "accounts", 204 Id: 0, 205 Update: M{"$inc": M{"balance": 100}}, 206 }} 207 208 err := s.runner.Run(ops, "", nil) 209 c.Assert(err, IsNil) 210 211 var account Account 212 err = s.accounts.FindId(0).One(&account) 213 c.Assert(err, IsNil) 214 c.Assert(account.Balance, Equals, 300) 215 216 err = s.runner.Run(ops, "", nil) 217 c.Assert(err, IsNil) 218 219 err = s.accounts.FindId(0).One(&account) 220 c.Assert(err, IsNil) 221 c.Assert(account.Balance, Equals, 400) 222 } 223 224 func (s *S) TestUpdateInsert(c *C) { 225 ops := []txn.Op{{ 226 C: "accounts", 227 Id: 0, 228 Update: M{"$inc": M{"balance": 100}}, 229 }, { 230 C: "accounts", 231 Id: 0, 232 Insert: M{"_id": 0, "balance": 200}, 233 }} 234 235 err := s.runner.Run(ops, "", nil) 236 c.Assert(err, IsNil) 237 238 var account Account 239 err = s.accounts.FindId(0).One(&account) 240 c.Assert(err, IsNil) 241 c.Assert(account.Balance, Equals, 200) 242 243 err = s.runner.Run(ops, "", nil) 244 c.Assert(err, IsNil) 245 246 err = s.accounts.FindId(0).One(&account) 247 c.Assert(err, IsNil) 248 c.Assert(account.Balance, Equals, 300) 249 } 250 251 func (s *S) TestInsertRemoveInsert(c *C) { 252 ops := []txn.Op{{ 253 C: "accounts", 254 Id: 0, 255 Insert: M{"_id": 0, "balance": 200}, 256 }, { 257 C: "accounts", 258 Id: 0, 259 Remove: true, 260 }, { 261 C: "accounts", 262 Id: 0, 263 Insert: M{"_id": 0, "balance": 300}, 264 }} 265 266 err := s.runner.Run(ops, "", nil) 267 c.Assert(err, IsNil) 268 269 var account Account 270 err = s.accounts.FindId(0).One(&account) 271 c.Assert(err, IsNil) 272 c.Assert(account.Balance, Equals, 300) 273 } 274 275 func (s *S) TestQueueStashing(c *C) { 276 txn.SetChaos(txn.Chaos{ 277 KillChance: 1, 278 Breakpoint: "set-applying", 279 }) 280 281 opses := [][]txn.Op{{{ 282 C: "accounts", 283 Id: 0, 284 Insert: M{"balance": 100}, 285 }}, {{ 286 C: "accounts", 287 Id: 0, 288 Remove: true, 289 }}, {{ 290 C: "accounts", 291 Id: 0, 292 Insert: M{"balance": 200}, 293 }}, {{ 294 C: "accounts", 295 Id: 0, 296 Update: M{"$inc": M{"balance": 100}}, 297 }}} 298 299 var last bson.ObjectId 300 for _, ops := range opses { 301 last = bson.NewObjectId() 302 err := s.runner.Run(ops, last, nil) 303 c.Assert(err, Equals, txn.ErrChaos) 304 } 305 306 txn.SetChaos(txn.Chaos{}) 307 err := s.runner.Resume(last) 308 c.Assert(err, IsNil) 309 310 var account Account 311 err = s.accounts.FindId(0).One(&account) 312 c.Assert(err, IsNil) 313 c.Assert(account.Balance, Equals, 300) 314 } 315 316 func (s *S) TestInfo(c *C) { 317 ops := []txn.Op{{ 318 C: "accounts", 319 Id: 0, 320 Assert: txn.DocMissing, 321 }} 322 323 id := bson.NewObjectId() 324 err := s.runner.Run(ops, id, M{"n": 42}) 325 c.Assert(err, IsNil) 326 327 var t struct{ I struct{ N int } } 328 err = s.tc.FindId(id).One(&t) 329 c.Assert(err, IsNil) 330 c.Assert(t.I.N, Equals, 42) 331 } 332 333 func (s *S) TestErrors(c *C) { 334 doc := bson.M{"foo": 1} 335 tests := []txn.Op{{ 336 C: "c", 337 Id: 0, 338 }, { 339 C: "c", 340 Id: 0, 341 Insert: doc, 342 Remove: true, 343 }, { 344 C: "c", 345 Id: 0, 346 Insert: doc, 347 Update: doc, 348 }, { 349 C: "c", 350 Id: 0, 351 Update: doc, 352 Remove: true, 353 }, { 354 C: "c", 355 Assert: doc, 356 }, { 357 Id: 0, 358 Assert: doc, 359 }} 360 361 txn.SetChaos(txn.Chaos{KillChance: 1.0}) 362 for _, op := range tests { 363 c.Logf("op: %v", op) 364 err := s.runner.Run([]txn.Op{op}, "", nil) 365 c.Assert(err, ErrorMatches, "error in transaction op 0: .*") 366 } 367 } 368 369 func (s *S) TestAssertNestedOr(c *C) { 370 // Assert uses $or internally. Ensure nesting works. 371 err := s.accounts.Insert(M{"_id": 0, "balance": 300}) 372 c.Assert(err, IsNil) 373 374 ops := []txn.Op{{ 375 C: "accounts", 376 Id: 0, 377 Assert: bson.D{{"$or", []bson.D{{{"balance", 100}}, {{"balance", 300}}}}}, 378 Update: bson.D{{"$inc", bson.D{{"balance", 100}}}}, 379 }} 380 381 err = s.runner.Run(ops, "", nil) 382 c.Assert(err, IsNil) 383 384 var account Account 385 err = s.accounts.FindId(0).One(&account) 386 c.Assert(err, IsNil) 387 c.Assert(account.Balance, Equals, 400) 388 } 389 390 func (s *S) TestVerifyFieldOrdering(c *C) { 391 // Used to have a map in certain operations, which means 392 // the ordering of fields would be messed up. 393 fields := bson.D{{"a", 1}, {"b", 2}, {"c", 3}} 394 ops := []txn.Op{{ 395 C: "accounts", 396 Id: 0, 397 Insert: fields, 398 }} 399 400 err := s.runner.Run(ops, "", nil) 401 c.Assert(err, IsNil) 402 403 var d bson.D 404 err = s.accounts.FindId(0).One(&d) 405 c.Assert(err, IsNil) 406 407 var filtered bson.D 408 for _, e := range d { 409 switch e.Name { 410 case "a", "b", "c": 411 filtered = append(filtered, e) 412 } 413 } 414 c.Assert(filtered, DeepEquals, fields) 415 } 416 417 func (s *S) TestChangeLog(c *C) { 418 chglog := s.db.C("chglog") 419 s.runner.ChangeLog(chglog) 420 421 ops := []txn.Op{{ 422 C: "debts", 423 Id: 0, 424 Assert: txn.DocMissing, 425 }, { 426 C: "accounts", 427 Id: 0, 428 Insert: M{"balance": 300}, 429 }, { 430 C: "accounts", 431 Id: 1, 432 Insert: M{"balance": 300}, 433 }, { 434 C: "people", 435 Id: "joe", 436 Insert: M{"accounts": []int64{0, 1}}, 437 }} 438 id := bson.NewObjectId() 439 err := s.runner.Run(ops, id, nil) 440 c.Assert(err, IsNil) 441 442 type IdList []interface{} 443 type Log struct { 444 Docs IdList "d" 445 Revnos []int64 "r" 446 } 447 var m map[string]*Log 448 err = chglog.FindId(id).One(&m) 449 c.Assert(err, IsNil) 450 451 c.Assert(m["accounts"], DeepEquals, &Log{IdList{0, 1}, []int64{2, 2}}) 452 c.Assert(m["people"], DeepEquals, &Log{IdList{"joe"}, []int64{2}}) 453 c.Assert(m["debts"], IsNil) 454 455 ops = []txn.Op{{ 456 C: "accounts", 457 Id: 0, 458 Update: M{"$inc": M{"balance": 100}}, 459 }, { 460 C: "accounts", 461 Id: 1, 462 Update: M{"$inc": M{"balance": 100}}, 463 }} 464 id = bson.NewObjectId() 465 err = s.runner.Run(ops, id, nil) 466 c.Assert(err, IsNil) 467 468 m = nil 469 err = chglog.FindId(id).One(&m) 470 c.Assert(err, IsNil) 471 472 c.Assert(m["accounts"], DeepEquals, &Log{IdList{0, 1}, []int64{3, 3}}) 473 c.Assert(m["people"], IsNil) 474 475 ops = []txn.Op{{ 476 C: "accounts", 477 Id: 0, 478 Remove: true, 479 }, { 480 C: "people", 481 Id: "joe", 482 Remove: true, 483 }} 484 id = bson.NewObjectId() 485 err = s.runner.Run(ops, id, nil) 486 c.Assert(err, IsNil) 487 488 m = nil 489 err = chglog.FindId(id).One(&m) 490 c.Assert(err, IsNil) 491 492 c.Assert(m["accounts"], DeepEquals, &Log{IdList{0}, []int64{-4}}) 493 c.Assert(m["people"], DeepEquals, &Log{IdList{"joe"}, []int64{-3}}) 494 } 495 496 func (s *S) TestPurgeMissing(c *C) { 497 txn.SetChaos(txn.Chaos{ 498 KillChance: 1, 499 Breakpoint: "set-applying", 500 }) 501 502 err := s.accounts.Insert(M{"_id": 0, "balance": 100}) 503 c.Assert(err, IsNil) 504 err = s.accounts.Insert(M{"_id": 1, "balance": 100}) 505 c.Assert(err, IsNil) 506 507 ops1 := []txn.Op{{ 508 C: "accounts", 509 Id: 3, 510 Insert: M{"balance": 100}, 511 }} 512 513 ops2 := []txn.Op{{ 514 C: "accounts", 515 Id: 0, 516 Remove: true, 517 }, { 518 C: "accounts", 519 Id: 1, 520 Update: M{"$inc": M{"balance": 100}}, 521 }, { 522 C: "accounts", 523 Id: 2, 524 Insert: M{"balance": 100}, 525 }} 526 527 first := bson.NewObjectId() 528 c.Logf("---- Running ops1 under transaction %q, to be canceled by chaos", first.Hex()) 529 err = s.runner.Run(ops1, first, nil) 530 c.Assert(err, Equals, txn.ErrChaos) 531 532 last := bson.NewObjectId() 533 c.Logf("---- Running ops2 under transaction %q, to be canceled by chaos", last.Hex()) 534 err = s.runner.Run(ops2, last, nil) 535 c.Assert(err, Equals, txn.ErrChaos) 536 537 c.Logf("---- Removing transaction %q", last.Hex()) 538 err = s.tc.RemoveId(last) 539 c.Assert(err, IsNil) 540 541 c.Logf("---- Disabling chaos and attempting to resume all") 542 txn.SetChaos(txn.Chaos{}) 543 err = s.runner.ResumeAll() 544 c.Assert(err, IsNil) 545 546 again := bson.NewObjectId() 547 c.Logf("---- Running ops2 again under transaction %q, to fail for missing transaction", again.Hex()) 548 err = s.runner.Run(ops2, again, nil) 549 c.Assert(err, ErrorMatches, "cannot find transaction .*") 550 551 c.Logf("---- Purging missing transactions") 552 err = s.runner.PurgeMissing("accounts") 553 c.Assert(err, IsNil) 554 555 c.Logf("---- Resuming pending transactions") 556 err = s.runner.ResumeAll() 557 c.Assert(err, IsNil) 558 559 expect := []struct{ Id, Balance int }{ 560 {0, -1}, 561 {1, 200}, 562 {2, 100}, 563 {3, 100}, 564 } 565 var got Account 566 for _, want := range expect { 567 err = s.accounts.FindId(want.Id).One(&got) 568 if want.Balance == -1 { 569 if err != mgo.ErrNotFound { 570 c.Errorf("Account %d should not exist, find got err=%#v", err) 571 } 572 } else if err != nil { 573 c.Errorf("Account %d should have balance of %d, but wasn't found", want.Id, want.Balance) 574 } else if got.Balance != want.Balance { 575 c.Errorf("Account %d should have balance of %d, got %d", want.Id, want.Balance, got.Balance) 576 } 577 } 578 } 579 580 func (s *S) TestTxnQueueStashStressTest(c *C) { 581 txn.SetChaos(txn.Chaos{ 582 SlowdownChance: 0.3, 583 Slowdown: 50 * time.Millisecond, 584 }) 585 defer txn.SetChaos(txn.Chaos{}) 586 587 // So we can run more iterations of the test in less time. 588 txn.SetDebug(false) 589 590 const runners = 10 591 const inserts = 10 592 const repeat = 100 593 594 for r := 0; r < repeat; r++ { 595 var wg sync.WaitGroup 596 wg.Add(runners) 597 for i := 0; i < runners; i++ { 598 go func(i, r int) { 599 defer wg.Done() 600 601 session := s.session.New() 602 defer session.Close() 603 runner := txn.NewRunner(s.tc.With(session)) 604 605 for j := 0; j < inserts; j++ { 606 ops := []txn.Op{{ 607 C: "accounts", 608 Id: fmt.Sprintf("insert-%d-%d", r, j), 609 Insert: bson.M{ 610 "added-by": i, 611 }, 612 }} 613 err := runner.Run(ops, "", nil) 614 if err != txn.ErrAborted { 615 c.Check(err, IsNil) 616 } 617 } 618 }(i, r) 619 } 620 wg.Wait() 621 } 622 } 623 624 func (s *S) TestPurgeMissingPipelineSizeLimit(c *C) { 625 // This test ensures that PurgeMissing can handle very large 626 // txn-queue fields. Previous iterations of PurgeMissing would 627 // trigger a 16MB aggregation pipeline result size limit when run 628 // against a documents or stashes with large numbers of txn-queue 629 // entries. PurgeMissing now no longer uses aggregation pipelines 630 // to work around this limit. 631 632 // The pipeline result size limitation was removed from MongoDB in 2.6 so 633 // this test is only run for older MongoDB version. 634 build, err := s.session.BuildInfo() 635 c.Assert(err, IsNil) 636 if build.VersionAtLeast(2, 6) { 637 c.Skip("This tests a problem that can only happen with MongoDB < 2.6 ") 638 } 639 640 // Insert a single document to work with. 641 err = s.accounts.Insert(M{"_id": 0, "balance": 100}) 642 c.Assert(err, IsNil) 643 644 ops := []txn.Op{{ 645 C: "accounts", 646 Id: 0, 647 Update: M{"$inc": M{"balance": 100}}, 648 }} 649 650 // Generate one successful transaction. 651 good := bson.NewObjectId() 652 c.Logf("---- Running ops under transaction %q", good.Hex()) 653 err = s.runner.Run(ops, good, nil) 654 c.Assert(err, IsNil) 655 656 // Generate another transaction which which will go missing. 657 missing := bson.NewObjectId() 658 c.Logf("---- Running ops under transaction %q (which will go missing)", missing.Hex()) 659 err = s.runner.Run(ops, missing, nil) 660 c.Assert(err, IsNil) 661 662 err = s.tc.RemoveId(missing) 663 c.Assert(err, IsNil) 664 665 // Generate a txn-queue on the test document that's large enough 666 // that it used to cause PurgeMissing to exceed MongoDB's pipeline 667 // result 16MB size limit (MongoDB 2.4 and older only). 668 // 669 // The contents of the txn-queue field doesn't matter, only that 670 // it's big enough to trigger the size limit. The required size 671 // can also be achieved by using multiple documents as long as the 672 // cumulative size of all the txn-queue fields exceeds the 673 // pipeline limit. A single document is easier to work with for 674 // this test however. 675 // 676 // The txn id of the successful transaction is used fill the 677 // txn-queue because this takes advantage of a short circuit in 678 // PurgeMissing, dramatically speeding up the test run time. 679 const fakeQueueLen = 250000 680 fakeTxnQueue := make([]string, fakeQueueLen) 681 token := good.Hex() + "_12345678" // txn id + nonce 682 for i := 0; i < fakeQueueLen; i++ { 683 fakeTxnQueue[i] = token 684 } 685 686 err = s.accounts.UpdateId(0, bson.M{ 687 "$set": bson.M{"txn-queue": fakeTxnQueue}, 688 }) 689 c.Assert(err, IsNil) 690 691 // PurgeMissing could hit the same pipeline result size limit when 692 // processing the txn-queue fields of stash documents so insert 693 // the large txn-queue there too to ensure that no longer happens. 694 err = s.sc.Insert( 695 bson.D{{"c", "accounts"}, {"id", 0}}, 696 bson.M{"txn-queue": fakeTxnQueue}, 697 ) 698 c.Assert(err, IsNil) 699 700 c.Logf("---- Purging missing transactions") 701 err = s.runner.PurgeMissing("accounts") 702 c.Assert(err, IsNil) 703 } 704 705 var flaky = flag.Bool("flaky", false, "Include flaky tests") 706 707 func (s *S) TestTxnQueueStressTest(c *C) { 708 // This fails about 20% of the time on Mongo 3.2 (I haven't tried 709 // other versions) with account balance being 3999 instead of 710 // 4000. That implies that some updates are being lost. This is 711 // bad and we'll need to chase it down in the near future - the 712 // only reason it's being skipped now is that it's already failing 713 // and it's better to have the txn tests running without this one 714 // than to have them not running at all. 715 if !*flaky { 716 c.Skip("Fails intermittently - disabling until fixed") 717 } 718 txn.SetChaos(txn.Chaos{ 719 SlowdownChance: 0.3, 720 Slowdown: 50 * time.Millisecond, 721 }) 722 defer txn.SetChaos(txn.Chaos{}) 723 724 // So we can run more iterations of the test in less time. 725 txn.SetDebug(false) 726 727 err := s.accounts.Insert(M{"_id": 0, "balance": 0}, M{"_id": 1, "balance": 0}) 728 c.Assert(err, IsNil) 729 730 // Run half of the operations changing account 0 and then 1, 731 // and the other half in the opposite order. 732 ops01 := []txn.Op{{ 733 C: "accounts", 734 Id: 0, 735 Update: M{"$inc": M{"balance": 1}}, 736 }, { 737 C: "accounts", 738 Id: 1, 739 Update: M{"$inc": M{"balance": 1}}, 740 }} 741 742 ops10 := []txn.Op{{ 743 C: "accounts", 744 Id: 1, 745 Update: M{"$inc": M{"balance": 1}}, 746 }, { 747 C: "accounts", 748 Id: 0, 749 Update: M{"$inc": M{"balance": 1}}, 750 }} 751 752 ops := [][]txn.Op{ops01, ops10} 753 754 const runners = 4 755 const changes = 1000 756 757 var wg sync.WaitGroup 758 wg.Add(runners) 759 for n := 0; n < runners; n++ { 760 n := n 761 go func() { 762 defer wg.Done() 763 for i := 0; i < changes; i++ { 764 err = s.runner.Run(ops[n%2], "", nil) 765 c.Assert(err, IsNil) 766 } 767 }() 768 } 769 wg.Wait() 770 771 for id := 0; id < 2; id++ { 772 var account Account 773 err = s.accounts.FindId(id).One(&account) 774 if account.Balance != runners*changes { 775 c.Errorf("Account should have balance of %d, got %d", runners*changes, account.Balance) 776 } 777 } 778 }