imgsrv/vendor/labix.org/v2/mgo/txn/txn_test.go

522 lines
10 KiB
Go

package txn_test
import (
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"labix.org/v2/mgo/txn"
. "launchpad.net/gocheck"
"testing"
)
func TestAll(t *testing.T) {
TestingT(t)
}
type S struct {
MgoSuite
db *mgo.Database
tc, sc *mgo.Collection
accounts *mgo.Collection
runner *txn.Runner
}
var _ = Suite(&S{})
type M map[string]interface{}
func (s *S) SetUpTest(c *C) {
txn.SetChaos(txn.Chaos{})
txn.SetLogger(c)
txn.SetDebug(true)
s.MgoSuite.SetUpTest(c)
s.db = s.session.DB("test")
s.tc = s.db.C("tc")
s.sc = s.db.C("tc.stash")
s.accounts = s.db.C("accounts")
s.runner = txn.NewRunner(s.tc)
}
type Account struct {
Id int `bson:"_id"`
Balance int
}
func (s *S) TestDocExists(c *C) {
err := s.accounts.Insert(M{"_id": 0, "balance": 300})
c.Assert(err, IsNil)
exists := []txn.Op{{
C: "accounts",
Id: 0,
Assert: txn.DocExists,
}}
missing := []txn.Op{{
C: "accounts",
Id: 0,
Assert: txn.DocMissing,
}}
err = s.runner.Run(exists, "", nil)
c.Assert(err, IsNil)
err = s.runner.Run(missing, "", nil)
c.Assert(err, Equals, txn.ErrAborted)
err = s.accounts.RemoveId(0)
c.Assert(err, IsNil)
err = s.runner.Run(exists, "", nil)
c.Assert(err, Equals, txn.ErrAborted)
err = s.runner.Run(missing, "", nil)
c.Assert(err, IsNil)
}
func (s *S) TestInsert(c *C) {
err := s.accounts.Insert(M{"_id": 0, "balance": 300})
c.Assert(err, IsNil)
ops := []txn.Op{{
C: "accounts",
Id: 0,
Insert: M{"balance": 200},
}}
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
ops[0].Id = 1
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
err = s.accounts.FindId(1).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 200)
}
func (s *S) TestRemove(c *C) {
err := s.accounts.Insert(M{"_id": 0, "balance": 300})
c.Assert(err, IsNil)
ops := []txn.Op{{
C: "accounts",
Id: 0,
Remove: true,
}}
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
err = s.accounts.FindId(0).One(nil)
c.Assert(err, Equals, mgo.ErrNotFound)
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
}
func (s *S) TestUpdate(c *C) {
var err error
err = s.accounts.Insert(M{"_id": 0, "balance": 200})
c.Assert(err, IsNil)
err = s.accounts.Insert(M{"_id": 1, "balance": 200})
c.Assert(err, IsNil)
ops := []txn.Op{{
C: "accounts",
Id: 0,
Update: M{"$inc": M{"balance": 100}},
}}
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
ops[0].Id = 1
err = s.accounts.FindId(1).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 200)
}
func (s *S) TestInsertUpdate(c *C) {
ops := []txn.Op{{
C: "accounts",
Id: 0,
Insert: M{"_id": 0, "balance": 200},
}, {
C: "accounts",
Id: 0,
Update: M{"$inc": M{"balance": 100}},
}}
err := s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 400)
}
func (s *S) TestUpdateInsert(c *C) {
ops := []txn.Op{{
C: "accounts",
Id: 0,
Update: M{"$inc": M{"balance": 100}},
}, {
C: "accounts",
Id: 0,
Insert: M{"_id": 0, "balance": 200},
}}
err := s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 200)
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
}
func (s *S) TestInsertRemoveInsert(c *C) {
ops := []txn.Op{{
C: "accounts",
Id: 0,
Insert: M{"_id": 0, "balance": 200},
}, {
C: "accounts",
Id: 0,
Remove: true,
}, {
C: "accounts",
Id: 0,
Insert: M{"_id": 0, "balance": 300},
}}
err := s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
}
func (s *S) TestQueueStashing(c *C) {
txn.SetChaos(txn.Chaos{
KillChance: 1,
Breakpoint: "set-applying",
})
opses := [][]txn.Op{{{
C: "accounts",
Id: 0,
Insert: M{"balance": 100},
}}, {{
C: "accounts",
Id: 0,
Remove: true,
}}, {{
C: "accounts",
Id: 0,
Insert: M{"balance": 200},
}}, {{
C: "accounts",
Id: 0,
Update: M{"$inc": M{"balance": 100}},
}}}
var last bson.ObjectId
for _, ops := range opses {
last = bson.NewObjectId()
err := s.runner.Run(ops, last, nil)
c.Assert(err, Equals, txn.ErrChaos)
}
txn.SetChaos(txn.Chaos{})
err := s.runner.Resume(last)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 300)
}
func (s *S) TestInfo(c *C) {
ops := []txn.Op{{
C: "accounts",
Id: 0,
Assert: txn.DocMissing,
}}
id := bson.NewObjectId()
err := s.runner.Run(ops, id, M{"n": 42})
c.Assert(err, IsNil)
var t struct{ I struct{ N int } }
err = s.tc.FindId(id).One(&t)
c.Assert(err, IsNil)
c.Assert(t.I.N, Equals, 42)
}
func (s *S) TestErrors(c *C) {
doc := bson.M{"foo": 1}
tests := []txn.Op{{
C: "c",
Id: 0,
}, {
C: "c",
Id: 0,
Insert: doc,
Remove: true,
}, {
C: "c",
Id: 0,
Insert: doc,
Update: doc,
}, {
C: "c",
Id: 0,
Update: doc,
Remove: true,
}, {
C: "c",
Assert: doc,
}, {
Id: 0,
Assert: doc,
}}
txn.SetChaos(txn.Chaos{KillChance: 1.0})
for _, op := range tests {
c.Logf("op: %v", op)
err := s.runner.Run([]txn.Op{op}, "", nil)
c.Assert(err, ErrorMatches, "error in transaction op 0: .*")
}
}
func (s *S) TestAssertNestedOr(c *C) {
// Assert uses $or internally. Ensure nesting works.
err := s.accounts.Insert(M{"_id": 0, "balance": 300})
c.Assert(err, IsNil)
ops := []txn.Op{{
C: "accounts",
Id: 0,
Assert: bson.D{{"$or", []bson.D{{{"balance", 100}}, {{"balance", 300}}}}},
Update: bson.D{{"$inc", bson.D{{"balance", 100}}}},
}}
err = s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var account Account
err = s.accounts.FindId(0).One(&account)
c.Assert(err, IsNil)
c.Assert(account.Balance, Equals, 400)
}
func (s *S) TestVerifyFieldOrdering(c *C) {
// Used to have a map in certain operations, which means
// the ordering of fields would be messed up.
fields := bson.D{{"a", 1}, {"b", 2}, {"c", 3}}
ops := []txn.Op{{
C: "accounts",
Id: 0,
Insert: fields,
}}
err := s.runner.Run(ops, "", nil)
c.Assert(err, IsNil)
var d bson.D
err = s.accounts.FindId(0).One(&d)
c.Assert(err, IsNil)
var filtered bson.D
for _, e := range d {
switch e.Name {
case "a", "b", "c":
filtered = append(filtered, e)
}
}
c.Assert(filtered, DeepEquals, fields)
}
func (s *S) TestChangeLog(c *C) {
chglog := s.db.C("chglog")
s.runner.ChangeLog(chglog)
ops := []txn.Op{{
C: "debts",
Id: 0,
Assert: txn.DocMissing,
}, {
C: "accounts",
Id: 0,
Insert: M{"balance": 300},
}, {
C: "accounts",
Id: 1,
Insert: M{"balance": 300},
}, {
C: "people",
Id: "joe",
Insert: M{"accounts": []int64{0, 1}},
}}
id := bson.NewObjectId()
err := s.runner.Run(ops, id, nil)
c.Assert(err, IsNil)
type IdList []interface{}
type Log struct {
Docs IdList "d"
Revnos []int64 "r"
}
var m map[string]*Log
err = chglog.FindId(id).One(&m)
c.Assert(err, IsNil)
c.Assert(m["accounts"], DeepEquals, &Log{IdList{0, 1}, []int64{2, 2}})
c.Assert(m["people"], DeepEquals, &Log{IdList{"joe"}, []int64{2}})
c.Assert(m["debts"], IsNil)
ops = []txn.Op{{
C: "accounts",
Id: 0,
Update: M{"$inc": M{"balance": 100}},
}, {
C: "accounts",
Id: 1,
Update: M{"$inc": M{"balance": 100}},
}}
id = bson.NewObjectId()
err = s.runner.Run(ops, id, nil)
c.Assert(err, IsNil)
m = nil
err = chglog.FindId(id).One(&m)
c.Assert(err, IsNil)
c.Assert(m["accounts"], DeepEquals, &Log{IdList{0, 1}, []int64{3, 3}})
c.Assert(m["people"], IsNil)
ops = []txn.Op{{
C: "accounts",
Id: 0,
Remove: true,
}, {
C: "people",
Id: "joe",
Remove: true,
}}
id = bson.NewObjectId()
err = s.runner.Run(ops, id, nil)
c.Assert(err, IsNil)
m = nil
err = chglog.FindId(id).One(&m)
c.Assert(err, IsNil)
c.Assert(m["accounts"], DeepEquals, &Log{IdList{0}, []int64{-4}})
c.Assert(m["people"], DeepEquals, &Log{IdList{"joe"}, []int64{-3}})
}
func (s *S) TestPurgeMissing(c *C) {
txn.SetChaos(txn.Chaos{
KillChance: 1,
Breakpoint: "set-applying",
})
err := s.accounts.Insert(M{"_id": 0, "balance": 100})
c.Assert(err, IsNil)
err = s.accounts.Insert(M{"_id": 1, "balance": 100})
c.Assert(err, IsNil)
ops1 := []txn.Op{{
C: "accounts",
Id: 3,
Insert: M{"balance": 100},
}}
ops2 := []txn.Op{{
C: "accounts",
Id: 0,
Remove: true,
}, {
C: "accounts",
Id: 1,
Update: M{"$inc": M{"balance": 100}},
}, {
C: "accounts",
Id: 2,
Insert: M{"balance": 100},
}}
err = s.runner.Run(ops1, "", nil)
c.Assert(err, Equals, txn.ErrChaos)
last := bson.NewObjectId()
err = s.runner.Run(ops2, last, nil)
c.Assert(err, Equals, txn.ErrChaos)
err = s.tc.RemoveId(last)
c.Assert(err, IsNil)
txn.SetChaos(txn.Chaos{})
err = s.runner.ResumeAll()
c.Assert(err, IsNil)
err = s.runner.Run(ops2, "", nil)
c.Assert(err, ErrorMatches, "cannot find transaction .*")
err = s.runner.PurgeMissing("accounts")
c.Assert(err, IsNil)
err = s.runner.Run(ops2, "", nil)
c.Assert(err, IsNil)
expect := []struct{ Id, Balance int }{
{0, -1},
{1, 200},
{2, 100},
{3, 100},
}
var got Account
for _, want := range expect {
err = s.accounts.FindId(want.Id).One(&got)
if want.Balance == -1 {
if err != mgo.ErrNotFound {
c.Errorf("Account %d should not exist, find got err=%#v", err)
}
} else if err != nil {
c.Errorf("Account %d should have balance of %d, but wasn't found", want.Id, want.Balance)
} else if got.Balance != want.Balance {
c.Errorf("Account %d should have balance of %d, got %d", want.Id, want.Balance, got.Balance)
}
}
}