Merge branch 'master' into 1971-show-image-thumbnails
[arvados.git] / services / keep / keep_test.go
index bbff19d0fbac0ca272e2a5a11a160589b1f22bc1..348445e78d105a79a136ea010c6139aad9ae4beb 100644 (file)
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "bytes"
        "fmt"
        "io/ioutil"
        "os"
@@ -12,6 +13,20 @@ var TEST_BLOCK = []byte("The quick brown fox jumps over the lazy dog.")
 var TEST_HASH = "e4d909c290d0fb1ca068ffaddf22cbd0"
 var BAD_BLOCK = []byte("The magic words are squeamish ossifrage.")
 
+// TODO(twp): Tests still to be written
+//
+//   * PutBlockFull
+//       - test that PutBlock returns 503 Full if the filesystem is full.
+//         (must mock FreeDiskSpace or Statfs? use a tmpfs?)
+//
+//   * PutBlockWriteErr
+//       - test the behavior when Write returns an error.
+//           - Possible solutions: use a small tmpfs and a high
+//             MIN_FREE_KILOBYTES to trick PutBlock into attempting
+//             to write a block larger than the amount of space left
+//           - use an interface to mock ioutil.TempFile with a File
+//             object that always returns an error on write
+//
 // ========================================
 // GetBlock tests.
 // ========================================
@@ -47,8 +62,8 @@ func TestGetBlockMissing(t *testing.T) {
 
        // Check that GetBlock returns failure.
        result, err := GetBlock(TEST_HASH)
-       if err == nil {
-               t.Errorf("GetBlock incorrectly returned success: ", result)
+       if err != NotFoundError {
+               t.Errorf("Expected NotFoundError, got %v", result)
        }
 }
 
@@ -68,8 +83,8 @@ func TestGetBlockCorrupt(t *testing.T) {
 
        // Check that GetBlock returns failure.
        result, err := GetBlock(TEST_HASH)
-       if err == nil {
-               t.Errorf("GetBlock incorrectly returned success: %s", result)
+       if err != CorruptError {
+               t.Errorf("Expected CorruptError, got %v", result)
        }
 }
 
@@ -93,7 +108,7 @@ func TestPutBlockOK(t *testing.T) {
 
        result, err := GetBlock(TEST_HASH)
        if err != nil {
-               t.Fatalf("GetBlock: %s", err.Error())
+               t.Fatalf("GetBlock returned error: %v", err)
        }
        if string(result) != string(TEST_BLOCK) {
                t.Error("PutBlock/GetBlock mismatch")
@@ -120,7 +135,7 @@ func TestPutBlockOneVol(t *testing.T) {
 
        result, err := GetBlock(TEST_HASH)
        if err != nil {
-               t.Fatalf("GetBlock: %s", err.Error())
+               t.Fatalf("GetBlock: %v", err)
        }
        if string(result) != string(TEST_BLOCK) {
                t.Error("PutBlock/GetBlock mismatch")
@@ -129,11 +144,11 @@ func TestPutBlockOneVol(t *testing.T) {
        }
 }
 
-// TestPutBlockCorrupt
+// TestPutBlockMD5Fail
 //     Check that PutBlock returns an error if passed a block and hash that
 //     do not match.
 //
-func TestPutBlockCorrupt(t *testing.T) {
+func TestPutBlockMD5Fail(t *testing.T) {
        defer teardown()
 
        // Create two test Keep volumes.
@@ -141,22 +156,69 @@ func TestPutBlockCorrupt(t *testing.T) {
 
        // Check that PutBlock returns the expected error when the hash does
        // not match the block.
-       if err := PutBlock(BAD_BLOCK, TEST_HASH); err == nil {
-               t.Error("PutBlock succeeded despite a block mismatch")
-       } else {
-               ke := err.(*KeepError)
-               if ke.HTTPCode != 401 || ke.Err.Error() != "MD5Fail" {
-                       t.Errorf("PutBlock returned the wrong error (%v)", ke)
-               }
+       if err := PutBlock(BAD_BLOCK, TEST_HASH); err != MD5Error {
+               t.Error("Expected MD5Error, got %v", err)
        }
 
        // Confirm that GetBlock fails to return anything.
-       if result, err := GetBlock(TEST_HASH); err == nil {
-               t.Errorf("GetBlock succeded after a corrupt block store, returned '%s'",
-                       string(result))
+       if result, err := GetBlock(TEST_HASH); err != NotFoundError {
+               t.Errorf("GetBlock succeeded after a corrupt block store (result = %s, err = %v)",
+                       string(result), err)
+       }
+}
+
+// TestPutBlockCorrupt
+//     PutBlock should overwrite corrupt blocks on disk when given
+//     a PUT request with a good block.
+//
+func TestPutBlockCorrupt(t *testing.T) {
+       defer teardown()
+
+       // Create two test Keep volumes.
+       KeepVolumes = setup(t, 2)
+
+       // Store a corrupted block under TEST_HASH.
+       store(t, KeepVolumes[0], TEST_HASH, BAD_BLOCK)
+       if err := PutBlock(TEST_BLOCK, TEST_HASH); err != nil {
+               t.Errorf("PutBlock: %v", err)
+       }
+
+       // The block on disk should now match TEST_BLOCK.
+       if block, err := GetBlock(TEST_HASH); err != nil {
+               t.Errorf("GetBlock: %v", err)
+       } else if bytes.Compare(block, TEST_BLOCK) != 0 {
+               t.Errorf("GetBlock returned: '%s'", string(block))
        }
 }
 
+// PutBlockCollision
+//     PutBlock returns a 400 Collision error when attempting to
+//     store a block that collides with another block on disk.
+//
+func TestPutBlockCollision(t *testing.T) {
+       defer teardown()
+
+       // These blocks both hash to the MD5 digest cee9a457e790cf20d4bdaa6d69f01e41.
+       var b1 = []byte("\x0e0eaU\x9a\xa7\x87\xd0\x0b\xc6\xf7\x0b\xbd\xfe4\x04\xcf\x03e\x9epO\x854\xc0\x0f\xfbe\x9cL\x87@\xcc\x94/\xeb-\xa1\x15\xa3\xf4\x15\\\xbb\x86\x07Is\x86em}\x1f4\xa4 Y\xd7\x8fZ\x8d\xd1\xef")
+       var b2 = []byte("\x0e0eaU\x9a\xa7\x87\xd0\x0b\xc6\xf7\x0b\xbd\xfe4\x04\xcf\x03e\x9etO\x854\xc0\x0f\xfbe\x9cL\x87@\xcc\x94/\xeb-\xa1\x15\xa3\xf4\x15\xdc\xbb\x86\x07Is\x86em}\x1f4\xa4 Y\xd7\x8fZ\x8d\xd1\xef")
+       var locator = "cee9a457e790cf20d4bdaa6d69f01e41"
+
+       // Prepare two test Keep volumes. Store one block,
+       // then attempt to store the other.
+       KeepVolumes = setup(t, 2)
+       store(t, KeepVolumes[1], locator, b1)
+
+       if err := PutBlock(b2, locator); err == nil {
+               t.Error("PutBlock did not report a collision")
+       } else if err != CollisionError {
+               t.Errorf("PutBlock returned %v", err)
+       }
+}
+
+// ========================================
+// FindKeepVolumes tests.
+// ========================================
+
 // TestFindKeepVolumes
 //     Confirms that FindKeepVolumes finds tmpfs volumes with "/keep"
 //     directories at the top level.
@@ -246,12 +308,13 @@ func teardown() {
        for _, vol := range KeepVolumes {
                os.RemoveAll(path.Dir(vol))
        }
+       KeepVolumes = nil
 }
 
 // store
 //     Low-level code to write Keep blocks directly to disk for testing.
 //
-func store(t *testing.T, keepdir string, filename string, block []byte) error {
+func store(t *testing.T, keepdir string, filename string, block []byte) {
        blockdir := fmt.Sprintf("%s/%s", keepdir, filename[:3])
        if err := os.MkdirAll(blockdir, 0755); err != nil {
                t.Fatal(err)
@@ -264,6 +327,4 @@ func store(t *testing.T, keepdir string, filename string, block []byte) error {
        } else {
                t.Fatal(err)
        }
-
-       return nil
 }