github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/blobserver/storagetest/storagetest.go (about)

     1  /*
     2  Copyright 2013 The Camlistore Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package storagetest tests blobserver.Storage implementations
    18  package storagetest
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"reflect"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"camlistore.org/pkg/blob"
    32  	"camlistore.org/pkg/blobserver"
    33  	"camlistore.org/pkg/context"
    34  	"camlistore.org/pkg/syncutil"
    35  	"camlistore.org/pkg/test"
    36  	"camlistore.org/pkg/types"
    37  )
    38  
    39  func Test(t *testing.T, fn func(*testing.T) (sto blobserver.Storage, cleanup func())) {
    40  	sto, cleanup := fn(t)
    41  	defer func() {
    42  		if t.Failed() {
    43  			t.Logf("test %T FAILED, skipping cleanup!", sto)
    44  		} else {
    45  			cleanup()
    46  		}
    47  	}()
    48  	t.Logf("Testing blobserver storage %T", sto)
    49  
    50  	t.Logf("Testing Enumerate for empty")
    51  	testEnumerate(t, sto, nil)
    52  
    53  	var blobs []*test.Blob
    54  	var blobRefs []blob.Ref
    55  	var blobSizedRefs []blob.SizedRef
    56  
    57  	contents := []string{"foo", "quux", "asdf", "qwerty", "0123456789"}
    58  	if !testing.Short() {
    59  		for i := 0; i < 95; i++ {
    60  			contents = append(contents, "foo-"+strconv.Itoa(i))
    61  		}
    62  	}
    63  	t.Logf("Testing receive")
    64  	for _, x := range contents {
    65  		b1 := &test.Blob{x}
    66  		b1s, err := sto.ReceiveBlob(b1.BlobRef(), b1.Reader())
    67  		if err != nil {
    68  			t.Fatalf("ReceiveBlob of %s: %v", b1, err)
    69  		}
    70  		if b1s != b1.SizedRef() {
    71  			t.Fatal("Received %v; want %v", b1s, b1.SizedRef())
    72  		}
    73  		blobs = append(blobs, b1)
    74  		blobRefs = append(blobRefs, b1.BlobRef())
    75  		blobSizedRefs = append(blobSizedRefs, b1.SizedRef())
    76  
    77  		switch len(blobSizedRefs) {
    78  		case 1, 5, 100:
    79  			t.Logf("Testing Enumerate for %d blobs", len(blobSizedRefs))
    80  			testEnumerate(t, sto, blobSizedRefs)
    81  		}
    82  	}
    83  	b1 := blobs[0]
    84  
    85  	// finish here if you want to examine the test directory
    86  	//t.Fatalf("FINISH")
    87  
    88  	t.Logf("Testing FetchStreaming")
    89  	for i, b2 := range blobs {
    90  		rc, size, err := sto.FetchStreaming(b2.BlobRef())
    91  		if err != nil {
    92  			t.Fatalf("error fetching %d. %s: %v", i, b2, err)
    93  		}
    94  		defer rc.Close()
    95  		testSizedBlob(t, rc, b2.BlobRef(), size)
    96  	}
    97  
    98  	if fetcher, ok := sto.(fetcher); ok {
    99  		rsc, size, err := fetcher.Fetch(b1.BlobRef())
   100  		if err != nil {
   101  			t.Fatalf("error fetching %s: %v", b1, err)
   102  		}
   103  		defer rsc.Close()
   104  		n, err := rsc.Seek(0, 0)
   105  		if err != nil {
   106  			t.Fatalf("error seeking in %s: %v", rsc, err)
   107  		}
   108  		if n != 0 {
   109  			t.Fatalf("after seeking to 0, we are at %d!", n)
   110  		}
   111  		testSizedBlob(t, rsc, b1.BlobRef(), size)
   112  	}
   113  
   114  	t.Logf("Testing Stat")
   115  	dest := make(chan blob.SizedRef)
   116  	go func() {
   117  		if err := sto.StatBlobs(dest, blobRefs); err != nil {
   118  			t.Fatalf("error stating blobs %s: %v", blobRefs, err)
   119  		}
   120  	}()
   121  	testStat(t, dest, blobSizedRefs)
   122  
   123  	// Enumerate tests.
   124  	sort.Sort(blob.SizedByRef(blobSizedRefs))
   125  
   126  	t.Logf("Testing Enumerate on all")
   127  	testEnumerate(t, sto, blobSizedRefs)
   128  
   129  	t.Logf("Testing Enumerate 'limit' param")
   130  	testEnumerate(t, sto, blobSizedRefs[:3], 3)
   131  
   132  	// Enumerate 'after'
   133  	{
   134  		after := blobSizedRefs[2].Ref.String()
   135  		t.Logf("Testing Enumerate 'after' param; after %q", after)
   136  		testEnumerate(t, sto, blobSizedRefs[3:], after)
   137  	}
   138  
   139  	// Enumerate 'after' + limit
   140  	{
   141  		after := blobSizedRefs[2].Ref.String()
   142  		t.Logf("Testing Enumerate 'after' + 'limit' param; after %q, limit 1", after)
   143  		testEnumerate(t, sto, blobSizedRefs[3:4], after, 1)
   144  	}
   145  
   146  	t.Logf("Testing Remove")
   147  	if err := sto.RemoveBlobs(blobRefs); err != nil {
   148  		if strings.Contains(err.Error(), "not implemented") {
   149  			t.Logf("RemoveBlob %s: %v", b1, err)
   150  		} else {
   151  			t.Fatalf("RemoveBlob %s: %v", b1, err)
   152  		}
   153  	}
   154  }
   155  
   156  type fetcher interface {
   157  	Fetch(blob blob.Ref) (types.ReadSeekCloser, int64, error)
   158  }
   159  
   160  func testSizedBlob(t *testing.T, r io.Reader, b1 blob.Ref, size int64) {
   161  	h := b1.Hash()
   162  	n, err := io.Copy(h, r)
   163  	if err != nil {
   164  		t.Fatalf("error reading from %s: %v", r, err)
   165  	}
   166  	if n != size {
   167  		t.Fatalf("read %d bytes from %s, metadata said %d!", n, size)
   168  	}
   169  	b2 := blob.RefFromHash(h)
   170  	if b2 != b1 {
   171  		t.Fatalf("content mismatch (awaited %s, got %s)", b1, b2)
   172  	}
   173  }
   174  
   175  func testEnumerate(t *testing.T, sto blobserver.Storage, wantUnsorted []blob.SizedRef, opts ...interface{}) {
   176  	var after string
   177  	var n = 1000
   178  	for _, opt := range opts {
   179  		switch v := opt.(type) {
   180  		case string:
   181  			after = v
   182  		case int:
   183  			n = v
   184  		default:
   185  			panic("bad option of type " + fmt.Sprint("%T", v))
   186  		}
   187  	}
   188  
   189  	want := append([]blob.SizedRef(nil), wantUnsorted...)
   190  	sort.Sort(blob.SizedByRef(want))
   191  
   192  	sbc := make(chan blob.SizedRef, 10)
   193  
   194  	var got []blob.SizedRef
   195  	var grp syncutil.Group
   196  	sawEnd := make(chan bool, 1)
   197  	grp.Go(func() error {
   198  		if err := sto.EnumerateBlobs(context.New(), sbc, after, n); err != nil {
   199  			return fmt.Errorf("EnumerateBlobs(%q, %d): %v", after, n)
   200  		}
   201  		return nil
   202  	})
   203  	grp.Go(func() error {
   204  		for sb := range sbc {
   205  			if !sb.Valid() {
   206  				return fmt.Errorf("invalid blobref %#v received in enumerate", sb)
   207  			}
   208  			got = append(got, sb)
   209  		}
   210  		sawEnd <- true
   211  		return nil
   212  
   213  	})
   214  	grp.Go(func() error {
   215  		select {
   216  		case <-sawEnd:
   217  			return nil
   218  		case <-time.After(10 * time.Second):
   219  			return errors.New("timeout waiting for EnumerateBlobs to close its channel")
   220  		}
   221  
   222  	})
   223  	if err := grp.Err(); err != nil {
   224  		t.Fatalf("Enumerate error: %v", err)
   225  		return
   226  	}
   227  	if len(got) == 0 && len(want) == 0 {
   228  		return
   229  	}
   230  	if !reflect.DeepEqual(got, want) {
   231  		t.Fatalf("Enumerate mismatch. Got %d; want %d.\n Got: %v\nWant: %v\n",
   232  			len(got), len(want), got, want)
   233  	}
   234  }
   235  
   236  func testStat(t *testing.T, enum <-chan blob.SizedRef, want []blob.SizedRef) {
   237  	// blobs may arrive in ANY order
   238  	m := make(map[string]int, len(want))
   239  	for i, sb := range want {
   240  		m[sb.Ref.String()] = i
   241  	}
   242  
   243  	i := 0
   244  	for sb := range enum {
   245  		if !sb.Valid() {
   246  			break
   247  		}
   248  		wanted := want[m[sb.Ref.String()]]
   249  		if wanted.Size != sb.Size {
   250  			t.Fatalf("received blob size is %d, wanted %d for &%d", sb.Size, wanted.Size, i)
   251  		}
   252  		if wanted.Ref != sb.Ref {
   253  			t.Fatalf("received blob ref mismatch &%d: wanted %s, got %s", i, sb.Ref, wanted.Ref)
   254  		}
   255  		i++
   256  		if i >= len(want) {
   257  			break
   258  		}
   259  	}
   260  }