package blockstore import ( "context" "testing" blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" syncds "github.com/ipfs/go-datastore/sync" ) var exampleBlock = blocks.NewBlock([]byte("foo")) func testArcCached(ctx context.Context, bs Blockstore) (*arccache, error) { if ctx == nil { ctx = context.TODO() } opts := DefaultCacheOpts() opts.HasBloomFilterSize = 0 opts.HasBloomFilterHashes = 0 bbs, err := CachedBlockstore(ctx, bs, opts) if err == nil { return bbs.(*arccache), nil } return nil, err } func createStores(t *testing.T) (*arccache, Blockstore, *callbackDatastore) { cd := &callbackDatastore{f: func() {}, ds: ds.NewMapDatastore()} bs := NewBlockstore(syncds.MutexWrap(cd)) arc, err := testArcCached(context.TODO(), bs) if err != nil { t.Fatal(err) } return arc, bs, cd } func trap(message string, cd *callbackDatastore, t *testing.T) { cd.SetFunc(func() { t.Fatal(message) }) } func untrap(cd *callbackDatastore) { cd.SetFunc(func() {}) } func TestRemoveCacheEntryOnDelete(t *testing.T) { arc, _, cd := createStores(t) arc.Put(exampleBlock) cd.Lock() writeHitTheDatastore := false cd.Unlock() cd.SetFunc(func() { writeHitTheDatastore = true }) arc.DeleteBlock(exampleBlock.Cid()) arc.Put(exampleBlock) if !writeHitTheDatastore { t.Fail() } } func TestElideDuplicateWrite(t *testing.T) { arc, _, cd := createStores(t) arc.Put(exampleBlock) trap("write hit datastore", cd, t) arc.Put(exampleBlock) } func TestHasRequestTriggersCache(t *testing.T) { arc, _, cd := createStores(t) arc.Has(exampleBlock.Cid()) trap("has hit datastore", cd, t) if has, err := arc.Has(exampleBlock.Cid()); has || err != nil { t.Fatal("has was true but there is no such block") } untrap(cd) err := arc.Put(exampleBlock) if err != nil { t.Fatal(err) } trap("has hit datastore", cd, t) if has, err := arc.Has(exampleBlock.Cid()); !has || err != nil { t.Fatal("has returned invalid result") } } func TestGetFillsCache(t *testing.T) { arc, _, cd := createStores(t) if bl, err := arc.Get(exampleBlock.Cid()); bl != nil || err == nil { t.Fatal("block was found or there was no error") } trap("has hit datastore", cd, t) if has, err := arc.Has(exampleBlock.Cid()); has || err != nil { t.Fatal("has was true but there is no such block") } if _, err := arc.GetSize(exampleBlock.Cid()); err != ErrNotFound { t.Fatal("getsize was true but there is no such block") } untrap(cd) if err := arc.Put(exampleBlock); err != nil { t.Fatal(err) } trap("has hit datastore", cd, t) if has, err := arc.Has(exampleBlock.Cid()); !has || err != nil { t.Fatal("has returned invalid result") } if blockSize, err := arc.GetSize(exampleBlock.Cid()); blockSize == -1 || err != nil { t.Fatal("getsize returned invalid result") } } func TestGetAndDeleteFalseShortCircuit(t *testing.T) { arc, _, cd := createStores(t) arc.Has(exampleBlock.Cid()) arc.GetSize(exampleBlock.Cid()) trap("get hit datastore", cd, t) if bl, err := arc.Get(exampleBlock.Cid()); bl != nil || err != ErrNotFound { t.Fatal("get returned invalid result") } if arc.DeleteBlock(exampleBlock.Cid()) != nil { t.Fatal("expected deletes to be idempotent") } } func TestArcCreationFailure(t *testing.T) { if arc, err := newARCCachedBS(context.TODO(), nil, -1); arc != nil || err == nil { t.Fatal("expected error and no cache") } } func TestInvalidKey(t *testing.T) { arc, _, _ := createStores(t) bl, err := arc.Get(cid.Cid{}) if bl != nil { t.Fatal("blocks should be nil") } if err == nil { t.Fatal("expected error") } } func TestHasAfterSucessfulGetIsCached(t *testing.T) { arc, bs, cd := createStores(t) bs.Put(exampleBlock) arc.Get(exampleBlock.Cid()) trap("has hit datastore", cd, t) arc.Has(exampleBlock.Cid()) } func TestGetSizeAfterSucessfulGetIsCached(t *testing.T) { arc, bs, cd := createStores(t) bs.Put(exampleBlock) arc.Get(exampleBlock.Cid()) trap("has hit datastore", cd, t) arc.GetSize(exampleBlock.Cid()) } func TestGetSizeMissingZeroSizeBlock(t *testing.T) { arc, bs, cd := createStores(t) emptyBlock := blocks.NewBlock([]byte{}) missingBlock := blocks.NewBlock([]byte("missingBlock")) bs.Put(emptyBlock) arc.Get(emptyBlock.Cid()) trap("has hit datastore", cd, t) if blockSize, err := arc.GetSize(emptyBlock.Cid()); blockSize != 0 || err != nil { t.Fatal("getsize returned invalid result") } untrap(cd) arc.Get(missingBlock.Cid()) trap("has hit datastore", cd, t) if _, err := arc.GetSize(missingBlock.Cid()); err != ErrNotFound { t.Fatal("getsize returned invalid result") } } func TestDifferentKeyObjectsWork(t *testing.T) { arc, bs, cd := createStores(t) bs.Put(exampleBlock) arc.Get(exampleBlock.Cid()) trap("has hit datastore", cd, t) cidstr := exampleBlock.Cid().String() ncid, err := cid.Decode(cidstr) if err != nil { t.Fatal(err) } arc.Has(ncid) } func TestPutManyCaches(t *testing.T) { arc, _, cd := createStores(t) arc.PutMany([]blocks.Block{exampleBlock}) trap("has hit datastore", cd, t) arc.Has(exampleBlock.Cid()) arc.GetSize(exampleBlock.Cid()) untrap(cd) arc.DeleteBlock(exampleBlock.Cid()) arc.Put(exampleBlock) trap("PunMany has hit datastore", cd, t) arc.PutMany([]blocks.Block{exampleBlock}) }