| Index: go/src/infra/gae/libs/memlock/memlock_test.go
|
| diff --git a/go/src/infra/gae/libs/memlock/memlock_test.go b/go/src/infra/gae/libs/memlock/memlock_test.go
|
| index 49b892b763715e3036b4af2baa50b1d66c8563df..6d86249507ea8ca3316bb2f45f7af8e1362e5b83 100644
|
| --- a/go/src/infra/gae/libs/memlock/memlock_test.go
|
| +++ b/go/src/infra/gae/libs/memlock/memlock_test.go
|
| @@ -8,6 +8,9 @@ import (
|
| "fmt"
|
| "infra/gae/libs/wrapper"
|
| "infra/gae/libs/wrapper/memory"
|
| + "infra/libs/clock"
|
| + "infra/libs/clock/testclock"
|
| + "runtime"
|
| "testing"
|
| "time"
|
|
|
| @@ -25,16 +28,28 @@ func init() {
|
| func TestSimple(t *testing.T) {
|
| // TODO(riannucci): Mock time.After so that we don't have to delay for real.
|
|
|
| + const key = memlockKeyPrefix + "testkey"
|
| +
|
| Convey("basic locking", t, func() {
|
| - c := memory.Use(context.Background())
|
| - mc := wrapper.GetMC(c).(interface {
|
| + start := time.Date(1986, time.October, 26, 1, 20, 00, 00, time.UTC)
|
| + ctx, clk := testclock.UseTime(context.Background(), start)
|
| + blocker := make(chan struct{})
|
| + clk.SetTimerCallback(func(clock.Timer) {
|
| + clk.Add(delay)
|
| + select {
|
| + case blocker <- struct{}{}:
|
| + default:
|
| + }
|
| + })
|
| + ctx = memory.Use(ctx)
|
| + mc := wrapper.GetMC(ctx).(interface {
|
| wrapper.Testable
|
| wrapper.MCSingleReadWriter
|
| })
|
|
|
| Convey("fails to acquire when memcache is down", func() {
|
| mc.BreakFeatures(nil, "Add")
|
| - err := TryWithLock(c, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| // should never reach here
|
| So(false, ShouldBeTrue)
|
| return nil
|
| @@ -44,48 +59,59 @@ func TestSimple(t *testing.T) {
|
|
|
| Convey("returns the inner error", func() {
|
| toRet := fmt.Errorf("sup")
|
| - err := TryWithLock(c, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| + return toRet
|
| + })
|
| + So(err, ShouldEqual, toRet)
|
| + })
|
| +
|
| + Convey("returns the error", func() {
|
| + toRet := fmt.Errorf("sup")
|
| + err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| return toRet
|
| })
|
| So(err, ShouldEqual, toRet)
|
| })
|
|
|
| Convey("can acquire when empty", func() {
|
| - err := TryWithLock(c, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| So(check(), ShouldBeTrue)
|
|
|
| + waitFalse := func() {
|
| + <-blocker
|
| + for i := 0; i < 3; i++ {
|
| + if check() {
|
| + runtime.Gosched()
|
| + }
|
| + }
|
| + So(check(), ShouldBeFalse)
|
| + }
|
| +
|
| Convey("waiting for a while keeps refreshing the lock", func() {
|
| - time.Sleep(memcacheLockTime * 8)
|
| + // simulate waiting for 64*delay time, and ensuring that checkLoop
|
| + // runs that many times.
|
| + for i := 0; i < 64; i++ {
|
| + <-blocker
|
| + clk.Add(delay)
|
| + }
|
| So(check(), ShouldBeTrue)
|
| })
|
|
|
| Convey("but sometimes we might lose it", func() {
|
| Convey("because it was evicted", func() {
|
| - mc.Delete(memlockKeyPrefix + "testkey")
|
| - time.Sleep(memcacheLockTime)
|
| - So(check(), ShouldBeFalse)
|
| - })
|
| -
|
| - Convey("because it got evicted (but we race)", func() {
|
| - mc.Set(&memcache.Item{
|
| - Key: memlockKeyPrefix + "testkey",
|
| - Value: []byte(""),
|
| - })
|
| + mc.Delete(key)
|
| + clk.Add(memcacheLockTime)
|
| + waitFalse()
|
| })
|
|
|
| Convey("or because it was stolen", func() {
|
| - mc.Set(&memcache.Item{
|
| - Key: memlockKeyPrefix + "testkey",
|
| - Value: []byte("wat"),
|
| - })
|
| - time.Sleep(memcacheLockTime)
|
| - So(check(), ShouldBeFalse)
|
| + mc.Set(&memcache.Item{Key: key, Value: []byte("wat")})
|
| + waitFalse()
|
| })
|
|
|
| Convey("or because of service issues", func() {
|
| mc.BreakFeatures(nil, "CompareAndSwap")
|
| - time.Sleep(memcacheLockTime)
|
| - So(check(), ShouldBeFalse)
|
| + waitFalse()
|
| })
|
| })
|
| return nil
|
| @@ -94,7 +120,7 @@ func TestSimple(t *testing.T) {
|
| })
|
|
|
| Convey("an empty context id is an error", func() {
|
| - So(TryWithLock(c, "testkey", "", nil), ShouldEqual, ErrEmptyClientID)
|
| + So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, ErrEmptyClientID)
|
| })
|
| })
|
| }
|
|
|