7179: more updates to address golint suggestions around the config parameters such...
[arvados.git] / services / keepstore / volume_test.go
index 66e0810c972a86c59603f3ab40498e264081072a..f272c84c837676b12cc45ad3b2e962f635e746b1 100644 (file)
@@ -1,6 +1,8 @@
 package main
 
 import (
+       "bytes"
+       "crypto/md5"
        "errors"
        "fmt"
        "io"
@@ -10,25 +12,57 @@ import (
        "time"
 )
 
+// A TestableVolume allows test suites to manipulate the state of an
+// underlying Volume, in order to test behavior in cases that are
+// impractical to achieve with a sequence of normal Volume operations.
+type TestableVolume interface {
+       Volume
+       // [Over]write content for a locator with the given data,
+       // bypassing all constraints like readonly and serialize.
+       PutRaw(locator string, data []byte)
+
+       // Specify the value Mtime() should return, until the next
+       // call to Touch, TouchWithDate, or Put.
+       TouchWithDate(locator string, lastPut time.Time)
+
+       // Clean up, delete temporary files.
+       Teardown()
+}
+
 // MockVolumes are test doubles for Volumes, used to test handlers.
 type MockVolume struct {
        Store      map[string][]byte
        Timestamps map[string]time.Time
+
        // Bad volumes return an error for every operation.
        Bad bool
+
        // Touchable volumes' Touch() method succeeds for a locator
        // that has been Put().
        Touchable bool
+
        // Readonly volumes return an error for Put, Delete, and
        // Touch.
        Readonly bool
-       called   map[string]int
-       mutex    sync.Mutex
+
+       // Gate is a "starting gate", allowing test cases to pause
+       // volume operations long enough to inspect state. Every
+       // operation (except Status) starts by receiving from
+       // Gate. Sending one value unblocks one operation; closing the
+       // channel unblocks all operations. By default, Gate is a
+       // closed channel, so all operations proceed without
+       // blocking. See trash_worker_test.go for an example.
+       Gate chan struct{}
+
+       called map[string]int
+       mutex  sync.Mutex
 }
 
 // CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock
 // volume.
 func CreateMockVolume() *MockVolume {
+       gate := make(chan struct{})
+       close(gate)
        return &MockVolume{
                Store:      make(map[string][]byte),
                Timestamps: make(map[string]time.Time),
@@ -36,6 +70,7 @@ func CreateMockVolume() *MockVolume {
                Touchable:  true,
                Readonly:   false,
                called:     map[string]int{},
+               Gate:       gate,
        }
 }
 
@@ -43,11 +78,11 @@ func CreateMockVolume() *MockVolume {
 func (v *MockVolume) CallCount(method string) int {
        v.mutex.Lock()
        defer v.mutex.Unlock()
-       if c, ok := v.called[method]; !ok {
+       c, ok := v.called[method]
+       if !ok {
                return 0
-       } else {
-               return c
        }
+       return c
 }
 
 func (v *MockVolume) gotCall(method string) {
@@ -60,18 +95,40 @@ func (v *MockVolume) gotCall(method string) {
        }
 }
 
+func (v *MockVolume) Compare(loc string, buf []byte) error {
+       v.gotCall("Compare")
+       <-v.Gate
+       if v.Bad {
+               return errors.New("Bad volume")
+       } else if block, ok := v.Store[loc]; ok {
+               if fmt.Sprintf("%x", md5.Sum(block)) != loc {
+                       return DiskHashError
+               }
+               if bytes.Compare(buf, block) != 0 {
+                       return CollisionError
+               }
+               return nil
+       } else {
+               return NotFoundError
+       }
+}
+
 func (v *MockVolume) Get(loc string) ([]byte, error) {
        v.gotCall("Get")
+       <-v.Gate
        if v.Bad {
                return nil, errors.New("Bad volume")
        } else if block, ok := v.Store[loc]; ok {
-               return block, nil
+               buf := bufs.Get(len(block))
+               copy(buf, block)
+               return buf, nil
        }
        return nil, os.ErrNotExist
 }
 
 func (v *MockVolume) Put(loc string, block []byte) error {
        v.gotCall("Put")
+       <-v.Gate
        if v.Bad {
                return errors.New("Bad volume")
        }
@@ -84,6 +141,7 @@ func (v *MockVolume) Put(loc string, block []byte) error {
 
 func (v *MockVolume) Touch(loc string) error {
        v.gotCall("Touch")
+       <-v.Gate
        if v.Readonly {
                return MethodDisabledError
        }
@@ -96,6 +154,7 @@ func (v *MockVolume) Touch(loc string) error {
 
 func (v *MockVolume) Mtime(loc string) (time.Time, error) {
        v.gotCall("Mtime")
+       <-v.Gate
        var mtime time.Time
        var err error
        if v.Bad {
@@ -110,6 +169,7 @@ func (v *MockVolume) Mtime(loc string) (time.Time, error) {
 
 func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
        v.gotCall("IndexTo")
+       <-v.Gate
        for loc, block := range v.Store {
                if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) {
                        continue
@@ -125,11 +185,12 @@ func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
 
 func (v *MockVolume) Delete(loc string) error {
        v.gotCall("Delete")
+       <-v.Gate
        if v.Readonly {
                return MethodDisabledError
        }
        if _, ok := v.Store[loc]; ok {
-               if time.Since(v.Timestamps[loc]) < blob_signature_ttl {
+               if time.Since(v.Timestamps[loc]) < blobSignatureTTL {
                        return nil
                }
                delete(v.Store, loc)