X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5b349796c7ddf23188c92dbe98e4ce75a2ac6ee6..20abe07b6780abb3a67292af4f4f8ffab988f9ca:/services/keepstore/volume_unix_test.go diff --git a/services/keepstore/volume_unix_test.go b/services/keepstore/volume_unix_test.go index 6bafa7c1ca..b47bedbb88 100644 --- a/services/keepstore/volume_unix_test.go +++ b/services/keepstore/volume_unix_test.go @@ -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()