7179: Start a set of generic volume tests.
[arvados.git] / services / keepstore / volume_unix_test.go
index 6bafa7c1ca03a23ff39037e95a656b27222696c9..b47bedbb885c23fc56e276fabf5151d4381a9164 100644 (file)
@@ -5,49 +5,70 @@ import (
        "fmt"
        "io/ioutil"
        "os"
+       "regexp"
+       "sort"
+       "strings"
        "syscall"
        "testing"
        "time"
 )
 
-func TempUnixVolume(t *testing.T, serialize bool, readonly bool) *UnixVolume {
+type TestableUnixVolume struct {
+       UnixVolume
+       t *testing.T
+}
+
+func NewTestableUnixVolume(t *testing.T, serialize bool, readonly bool) *TestableUnixVolume {
        d, err := ioutil.TempDir("", "volume_test")
        if err != nil {
                t.Fatal(err)
        }
-       return &UnixVolume{
-               root:      d,
-               serialize: serialize,
-               readonly:  readonly,
+       return &TestableUnixVolume{
+               UnixVolume: UnixVolume{
+                       root:      d,
+                       serialize: serialize,
+                       readonly:  readonly,
+               },
+               t: t,
        }
 }
 
-func _teardown(v *UnixVolume) {
-       os.RemoveAll(v.root)
+// PutRaw writes a Keep block directly into a UnixVolume, even if
+// the volume is readonly.
+func (v *TestableUnixVolume) PutRaw(locator string, data []byte) {
+       defer func(orig bool) {
+               v.readonly = orig
+       }(v.readonly)
+       v.readonly = false
+       err := v.Put(locator, data)
+       if err != nil {
+               v.t.Fatal(err)
+       }
 }
 
-// _store writes a Keep block directly into a UnixVolume, bypassing
-// the overhead and safeguards of Put(). Useful for storing bogus data
-// and isolating unit tests from Put() behavior.
-func _store(t *testing.T, vol *UnixVolume, filename string, block []byte) {
-       blockdir := fmt.Sprintf("%s/%s", vol.root, filename[:3])
-       if err := os.MkdirAll(blockdir, 0755); err != nil {
-               t.Fatal(err)
+func (v *TestableUnixVolume) TouchWithDate(locator string, lastPut time.Time) {
+       err := syscall.Utime(v.blockPath(locator), &syscall.Utimbuf{lastPut.Unix(), lastPut.Unix()})
+       if err != nil {
+               v.t.Fatal(err)
        }
+}
 
-       blockpath := fmt.Sprintf("%s/%s", blockdir, filename)
-       if f, err := os.Create(blockpath); err == nil {
-               f.Write(block)
-               f.Close()
-       } else {
-               t.Fatal(err)
+func (v *TestableUnixVolume) Teardown() {
+       if err := os.RemoveAll(v.root); err != nil {
+               v.t.Fatal(err)
        }
 }
 
+func TestUnixVolumeWithGenericTests(t *testing.T) {
+       DoGenericVolumeTests(t, func(t *testing.T) TestableVolume {
+               return NewTestableUnixVolume(t, false, false)
+       })
+}
+
 func TestGet(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
-       _store(t, v, TEST_HASH, TEST_BLOCK)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
+       v.Put(TEST_HASH, TEST_BLOCK)
 
        buf, err := v.Get(TEST_HASH)
        if err != nil {
@@ -59,9 +80,9 @@ func TestGet(t *testing.T) {
 }
 
 func TestGetNotFound(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
-       _store(t, v, TEST_HASH, TEST_BLOCK)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
+       v.Put(TEST_HASH, TEST_BLOCK)
 
        buf, err := v.Get(TEST_HASH_2)
        switch {
@@ -74,9 +95,45 @@ func TestGetNotFound(t *testing.T) {
        }
 }
 
+func TestIndexTo(t *testing.T) {
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
+
+       v.Put(TEST_HASH, TEST_BLOCK)
+       v.Put(TEST_HASH_2, TEST_BLOCK_2)
+       v.Put(TEST_HASH_3, TEST_BLOCK_3)
+
+       buf := new(bytes.Buffer)
+       v.IndexTo("", buf)
+       index_rows := strings.Split(string(buf.Bytes()), "\n")
+       sort.Strings(index_rows)
+       sorted_index := strings.Join(index_rows, "\n")
+       m, err := regexp.MatchString(
+               `^\n`+TEST_HASH+`\+\d+ \d+\n`+
+                       TEST_HASH_3+`\+\d+ \d+\n`+
+                       TEST_HASH_2+`\+\d+ \d+$`,
+               sorted_index)
+       if err != nil {
+               t.Error(err)
+       } else if !m {
+               t.Errorf("Got index %q for empty prefix", sorted_index)
+       }
+
+       for _, prefix := range []string{"f", "f15", "f15ac"} {
+               buf = new(bytes.Buffer)
+               v.IndexTo(prefix, buf)
+               m, err := regexp.MatchString(`^`+TEST_HASH_2+`\+\d+ \d+\n$`, string(buf.Bytes()))
+               if err != nil {
+                       t.Error(err)
+               } else if !m {
+                       t.Errorf("Got index %q for prefix %q", string(buf.Bytes()), prefix)
+               }
+       }
+}
+
 func TestPut(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
 
        err := v.Put(TEST_HASH, TEST_BLOCK)
        if err != nil {
@@ -92,8 +149,8 @@ func TestPut(t *testing.T) {
 }
 
 func TestPutBadVolume(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
 
        os.Chmod(v.root, 000)
        err := v.Put(TEST_HASH, TEST_BLOCK)
@@ -103,35 +160,29 @@ func TestPutBadVolume(t *testing.T) {
 }
 
 func TestUnixVolumeReadonly(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, true)
+       defer v.Teardown()
 
-       // First write something before marking readonly
-       err := v.Put(TEST_HASH, TEST_BLOCK)
-       if err != nil {
-               t.Error("got err %v, expected nil", err)
-       }
+       v.PutRaw(TEST_HASH, TEST_BLOCK)
 
-       v.readonly = true
-
-       _, err = v.Get(TEST_HASH)
+       _, err := v.Get(TEST_HASH)
        if err != nil {
-               t.Error("got err %v, expected nil", err)
+               t.Errorf("got err %v, expected nil", err)
        }
 
        err = v.Put(TEST_HASH, TEST_BLOCK)
        if err != MethodDisabledError {
-               t.Error("got err %v, expected MethodDisabledError", err)
+               t.Errorf("got err %v, expected MethodDisabledError", err)
        }
 
        err = v.Touch(TEST_HASH)
        if err != MethodDisabledError {
-               t.Error("got err %v, expected MethodDisabledError", err)
+               t.Errorf("got err %v, expected MethodDisabledError", err)
        }
 
        err = v.Delete(TEST_HASH)
        if err != MethodDisabledError {
-               t.Error("got err %v, expected MethodDisabledError", err)
+               t.Errorf("got err %v, expected MethodDisabledError", err)
        }
 }
 
@@ -139,8 +190,8 @@ func TestUnixVolumeReadonly(t *testing.T) {
 //     Test that when applying PUT to a block that already exists,
 //     the block's modification time is updated.
 func TestPutTouch(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
 
        if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
                t.Error(err)
@@ -154,17 +205,11 @@ func TestPutTouch(t *testing.T) {
        // Set the stored block's mtime far enough in the past that we
        // can see the difference between "timestamp didn't change"
        // and "timestamp granularity is too low".
-       {
-               oldtime := time.Now().Add(-20 * time.Second).Unix()
-               if err := syscall.Utime(v.blockPath(TEST_HASH),
-                       &syscall.Utimbuf{oldtime, oldtime}); err != nil {
-                       t.Error(err)
-               }
+       v.TouchWithDate(TEST_HASH, time.Now().Add(-20*time.Second))
 
-               // Make sure v.Mtime() agrees the above Utime really worked.
-               if t0, err := v.Mtime(TEST_HASH); err != nil || t0.IsZero() || !t0.Before(threshold) {
-                       t.Errorf("Setting mtime failed: %v, %v", t0, err)
-               }
+       // Make sure v.Mtime() agrees the above Utime really worked.
+       if t0, err := v.Mtime(TEST_HASH); err != nil || t0.IsZero() || !t0.Before(threshold) {
+               t.Errorf("Setting mtime failed: %v, %v", t0, err)
        }
 
        // Write the same block again.
@@ -173,13 +218,10 @@ func TestPutTouch(t *testing.T) {
        }
 
        // Verify threshold < t1
-       t1, err := v.Mtime(TEST_HASH)
-       if err != nil {
+       if t1, err := v.Mtime(TEST_HASH); err != nil {
                t.Error(err)
-       }
-       if t1.Before(threshold) {
-               t.Errorf("t1 %v must be >= threshold %v after v.Put ",
-                       t1, threshold)
+       } else if t1.Before(threshold) {
+               t.Errorf("t1 %v should be >= threshold %v after v.Put ", t1, threshold)
        }
 }
 
@@ -199,12 +241,12 @@ func TestPutTouch(t *testing.T) {
 //
 func TestGetSerialized(t *testing.T) {
        // Create a volume with I/O serialization enabled.
-       v := TempUnixVolume(t, true, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, true, false)
+       defer v.Teardown()
 
-       _store(t, v, TEST_HASH, TEST_BLOCK)
-       _store(t, v, TEST_HASH_2, TEST_BLOCK_2)
-       _store(t, v, TEST_HASH_3, TEST_BLOCK_3)
+       v.Put(TEST_HASH, TEST_BLOCK)
+       v.Put(TEST_HASH_2, TEST_BLOCK_2)
+       v.Put(TEST_HASH_3, TEST_BLOCK_3)
 
        sem := make(chan int)
        go func(sem chan int) {
@@ -248,8 +290,8 @@ func TestGetSerialized(t *testing.T) {
 
 func TestPutSerialized(t *testing.T) {
        // Create a volume with I/O serialization enabled.
-       v := TempUnixVolume(t, true, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, true, false)
+       defer v.Teardown()
 
        sem := make(chan int)
        go func(sem chan int) {
@@ -277,7 +319,7 @@ func TestPutSerialized(t *testing.T) {
        }(sem)
 
        // Wait for all goroutines to finish
-       for done := 0; done < 2; {
+       for done := 0; done < 3; {
                done += <-sem
        }
 
@@ -308,8 +350,8 @@ func TestPutSerialized(t *testing.T) {
 }
 
 func TestIsFull(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
 
        full_path := v.root + "/full"
        now := fmt.Sprintf("%d", time.Now().Unix())
@@ -328,8 +370,8 @@ func TestIsFull(t *testing.T) {
 }
 
 func TestNodeStatus(t *testing.T) {
-       v := TempUnixVolume(t, false, false)
-       defer _teardown(v)
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
 
        // Get node status and make a basic sanity check.
        volinfo := v.Status()