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) |
}) |
}) |
} |