-package main
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package keepstore
import (
- "errors"
- "fmt"
- "os"
- "strings"
"sync"
"time"
)
-// 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
-}
+var (
+ TestBlock = []byte("The quick brown fox jumps over the lazy dog.")
+ TestHash = "e4d909c290d0fb1ca068ffaddf22cbd0"
-// CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock
-// volume.
-func CreateMockVolume() *MockVolume {
- return &MockVolume{
- Store: make(map[string][]byte),
- Timestamps: make(map[string]time.Time),
- Bad: false,
- Touchable: true,
- Readonly: false,
- called: map[string]int{},
- }
-}
+ TestBlock2 = []byte("Pack my box with five dozen liquor jugs.")
+ TestHash2 = "f15ac516f788aec4f30932ffb6395c39"
-// CallCount returns how many times the named method has been called.
-func (v *MockVolume) CallCount(method string) int {
- v.mutex.Lock()
- defer v.mutex.Unlock()
- if c, ok := v.called[method]; !ok {
- return 0
- } else {
- return c
- }
-}
+ TestBlock3 = []byte("Now is the time for all good men to come to the aid of their country.")
+ TestHash3 = "eed29bbffbc2dbe5e5ee0bb71888e61f"
-func (v *MockVolume) gotCall(method string) {
- v.mutex.Lock()
- defer v.mutex.Unlock()
- if _, ok := v.called[method]; !ok {
- v.called[method] = 1
- } else {
- v.called[method]++
- }
-}
+ EmptyHash = "d41d8cd98f00b204e9800998ecf8427e"
+ EmptyBlock = []byte("")
+)
-func (v *MockVolume) Get(loc string) ([]byte, error) {
- v.gotCall("Get")
- if v.Bad {
- return nil, errors.New("Bad volume")
- } else if block, ok := v.Store[loc]; ok {
- return block, nil
- }
- return nil, os.ErrNotExist
-}
+// 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
-func (v *MockVolume) Put(loc string, block []byte) error {
- v.gotCall("Put")
- if v.Bad {
- return errors.New("Bad volume")
- }
- if v.Readonly {
- return MethodDisabledError
- }
- v.Store[loc] = block
- return v.Touch(loc)
-}
+ // Returns the strings that a driver uses to record read/write operations.
+ ReadWriteOperationLabelValues() (r, w string)
-func (v *MockVolume) Touch(loc string) error {
- v.gotCall("Touch")
- if v.Readonly {
- return MethodDisabledError
- }
- if v.Touchable {
- v.Timestamps[loc] = time.Now()
- return nil
- }
- return errors.New("Touch failed")
+ // Specify the value Mtime() should return, until the next
+ // call to Touch, TouchWithDate, or BlockWrite.
+ TouchWithDate(locator string, lastBlockWrite time.Time)
+
+ // Clean up, delete temporary files.
+ Teardown()
}
-func (v *MockVolume) Mtime(loc string) (time.Time, error) {
- v.gotCall("Mtime")
- var mtime time.Time
- var err error
- if v.Bad {
- err = errors.New("Bad volume")
- } else if t, ok := v.Timestamps[loc]; ok {
- mtime = t
- } else {
- err = os.ErrNotExist
- }
- return mtime, err
+// brbuffer is like bytes.Buffer, but it implements io.WriterAt.
+// Convenient for testing (volume)BlockRead implementations.
+type brbuffer struct {
+ mtx sync.Mutex
+ buf []byte
}
-func (v *MockVolume) Index(prefix string) string {
- v.gotCall("Index")
- var result string
- for loc, block := range v.Store {
- if IsValidLocator(loc) && strings.HasPrefix(loc, prefix) {
- result = result + fmt.Sprintf("%s+%d %d\n",
- loc, len(block), 123456789)
- }
+func (b *brbuffer) WriteAt(p []byte, offset int64) (int, error) {
+ b.mtx.Lock()
+ defer b.mtx.Unlock()
+ if short := int(offset) + len(p) - len(b.buf); short > 0 {
+ b.buf = append(b.buf, make([]byte, short)...)
}
- return result
+ return copy(b.buf[offset:], p), nil
}
-func (v *MockVolume) Delete(loc string) error {
- v.gotCall("Delete")
- if v.Readonly {
- return MethodDisabledError
- }
- if _, ok := v.Store[loc]; ok {
- if time.Since(v.Timestamps[loc]) < permission_ttl {
- return nil
- }
- delete(v.Store, loc)
- return nil
- }
- return os.ErrNotExist
+func (b *brbuffer) Bytes() []byte {
+ b.mtx.Lock()
+ defer b.mtx.Unlock()
+ return b.buf
}
-func (v *MockVolume) Status() *VolumeStatus {
- var used uint64
- for _, block := range v.Store {
- used = used + uint64(len(block))
- }
- return &VolumeStatus{"/bogo", 123, 1000000 - used, used}
+func (b *brbuffer) String() string {
+ b.mtx.Lock()
+ defer b.mtx.Unlock()
+ return string(b.buf)
}
-func (v *MockVolume) String() string {
- return "[MockVolume]"
+func (b *brbuffer) Len() int {
+ b.mtx.Lock()
+ defer b.mtx.Unlock()
+ return len(b.buf)
}
-func (v *MockVolume) Writable() bool {
- return !v.Readonly
+func (b *brbuffer) Reset() {
+ b.mtx.Lock()
+ defer b.mtx.Unlock()
+ b.buf = nil
}
+
+// a brdiscarder is like io.Discard, but it implements
+// io.WriterAt. Convenient for testing (volume)BlockRead
+// implementations when the output is not checked.
+type brdiscarder struct{}
+
+func (brdiscarder) WriteAt(p []byte, offset int64) (int, error) { return len(p), nil }
+
+var brdiscard = brdiscarder{}