| 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
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..49b892b763715e3036b4af2baa50b1d66c8563df
 | 
| --- /dev/null
 | 
| +++ b/go/src/infra/gae/libs/memlock/memlock_test.go
 | 
| @@ -0,0 +1,100 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package memlock
 | 
| +
 | 
| +import (
 | 
| +	"fmt"
 | 
| +	"infra/gae/libs/wrapper"
 | 
| +	"infra/gae/libs/wrapper/memory"
 | 
| +	"testing"
 | 
| +	"time"
 | 
| +
 | 
| +	. "github.com/smartystreets/goconvey/convey"
 | 
| +	"golang.org/x/net/context"
 | 
| +
 | 
| +	"appengine/memcache"
 | 
| +)
 | 
| +
 | 
| +func init() {
 | 
| +	delay = time.Millisecond
 | 
| +	memcacheLockTime = time.Millisecond * 4
 | 
| +}
 | 
| +
 | 
| +func TestSimple(t *testing.T) {
 | 
| +	// TODO(riannucci): Mock time.After so that we don't have to delay for real.
 | 
| +
 | 
| +	Convey("basic locking", t, func() {
 | 
| +		c := memory.Use(context.Background())
 | 
| +		mc := wrapper.GetMC(c).(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 {
 | 
| +				// should never reach here
 | 
| +				So(false, ShouldBeTrue)
 | 
| +				return nil
 | 
| +			})
 | 
| +			So(err, ShouldEqual, ErrFailedToLock)
 | 
| +		})
 | 
| +
 | 
| +		Convey("returns the inner error", func() {
 | 
| +			toRet := fmt.Errorf("sup")
 | 
| +			err := TryWithLock(c, "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 {
 | 
| +				So(check(), ShouldBeTrue)
 | 
| +
 | 
| +				Convey("waiting for a while keeps refreshing the lock", func() {
 | 
| +					time.Sleep(memcacheLockTime * 8)
 | 
| +					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(""),
 | 
| +						})
 | 
| +					})
 | 
| +
 | 
| +					Convey("or because it was stolen", func() {
 | 
| +						mc.Set(&memcache.Item{
 | 
| +							Key:   memlockKeyPrefix + "testkey",
 | 
| +							Value: []byte("wat"),
 | 
| +						})
 | 
| +						time.Sleep(memcacheLockTime)
 | 
| +						So(check(), ShouldBeFalse)
 | 
| +					})
 | 
| +
 | 
| +					Convey("or because of service issues", func() {
 | 
| +						mc.BreakFeatures(nil, "CompareAndSwap")
 | 
| +						time.Sleep(memcacheLockTime)
 | 
| +						So(check(), ShouldBeFalse)
 | 
| +					})
 | 
| +				})
 | 
| +				return nil
 | 
| +			})
 | 
| +			So(err, ShouldBeNil)
 | 
| +		})
 | 
| +
 | 
| +		Convey("an empty context id is an error", func() {
 | 
| +			So(TryWithLock(c, "testkey", "", nil), ShouldEqual, ErrEmptyClientID)
 | 
| +		})
 | 
| +	})
 | 
| +}
 | 
| 
 |