From 8dd54802b2ada6da4fd6faac6e4fba2e82d2fb11 Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Tue, 8 Sep 2015 00:08:14 -0400 Subject: [PATCH] 7179: Add TestableVolume interface, make UnixVolume tests a bit more reusable. --- services/keepstore/volume_test.go | 15 ++ services/keepstore/volume_unix_test.go | 186 ++++++++++++++----------- 2 files changed, 123 insertions(+), 78 deletions(-) diff --git a/services/keepstore/volume_test.go b/services/keepstore/volume_test.go index c5a7491b3d..34dcbdc8cf 100644 --- a/services/keepstore/volume_test.go +++ b/services/keepstore/volume_test.go @@ -10,6 +10,21 @@ 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 diff --git a/services/keepstore/volume_unix_test.go b/services/keepstore/volume_unix_test.go index ebb8421d9e..83df9ce10b 100644 --- a/services/keepstore/volume_unix_test.go +++ b/services/keepstore/volume_unix_test.go @@ -13,44 +13,56 @@ import ( "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 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 { @@ -62,9 +74,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 { @@ -78,12 +90,12 @@ func TestGetNotFound(t *testing.T) { } func TestIndexTo(t *testing.T) { - v := TempUnixVolume(t, false, false) - defer _teardown(v) + v := NewTestableUnixVolume(t, false, 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) buf := new(bytes.Buffer) v.IndexTo("", buf) @@ -114,8 +126,8 @@ func TestIndexTo(t *testing.T) { } 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 { @@ -131,8 +143,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) @@ -142,35 +154,62 @@ 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.readonly = true + v.PutRaw(TEST_HASH, TEST_BLOCK) - _, 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) + } +} + +func TestUnixVolumeDeleteNewBlock(t *testing.T) { + v := NewTestableUnixVolume(t, false, false) + defer v.Teardown() + + v.Put(TEST_HASH, TEST_BLOCK) + + if err := v.Delete(TEST_HASH); err != nil { + t.Error(err) + } + // This isn't reported as an error, but the block should not + // have been deleted: it's newer than blob_signature_ttl. + if data, err := v.Get(TEST_HASH); err != nil { + t.Error(err) + } else if bytes.Compare(data, TEST_BLOCK) != 0 { + t.Error("Content incorrect after delete failed") + } +} + +func TestUnixVolumeDeleteOldBlock(t *testing.T) { + v := NewTestableUnixVolume(t, false, false) + defer v.Teardown() + + v.Put(TEST_HASH, TEST_BLOCK) + v.TouchWithDate(TEST_HASH, time.Now().Add(-2*blob_signature_ttl*time.Second)) + + if err := v.Delete(TEST_HASH); err != nil { + t.Error(err) + } + if _, err := v.Get(TEST_HASH); !os.IsNotExist(err) { + t.Errorf("os.IsNotExist(%v) should have been true", err.Error()) } } @@ -178,8 +217,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) @@ -193,17 +232,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. @@ -212,13 +245,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) } } @@ -238,12 +268,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) { @@ -287,8 +317,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) { @@ -347,8 +377,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()) @@ -367,8 +397,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() -- 2.30.2