github.com/creachadair/ffs@v0.17.3/storage/wbstore/wbstore_test.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package wbstore_test
    16  
    17  import (
    18  	"context"
    19  	"sort"
    20  	"testing"
    21  
    22  	"github.com/creachadair/ffs/blob"
    23  	"github.com/creachadair/ffs/blob/memstore"
    24  	"github.com/creachadair/ffs/blob/storetest"
    25  	"github.com/creachadair/ffs/storage/wbstore"
    26  	"github.com/google/go-cmp/cmp"
    27  )
    28  
    29  type slowKV struct {
    30  	blob.KV
    31  	next <-chan chan struct{}
    32  }
    33  
    34  func (s slowKV) Put(ctx context.Context, opts blob.PutOptions) error {
    35  	select {
    36  	case <-ctx.Done():
    37  		return ctx.Err()
    38  	case p := <-s.next:
    39  		defer close(p)
    40  		return s.KV.Put(ctx, opts)
    41  	}
    42  }
    43  
    44  func (s slowKV) CASPut(ctx context.Context, data []byte) (string, error) {
    45  	select {
    46  	case <-ctx.Done():
    47  		return "", ctx.Err()
    48  	case p := <-s.next:
    49  		defer close(p)
    50  		return blob.CASFromKV(s.KV).CASPut(ctx, data)
    51  	}
    52  }
    53  
    54  func TestStore(t *testing.T) {
    55  	ctx := t.Context()
    56  
    57  	phys := memstore.NewKV() // represents the "physical" storage at the far end
    58  
    59  	next := make(chan chan struct{}, 1)
    60  	base := memstore.New(func() blob.KV {
    61  		return slowKV{KV: phys, next: next}
    62  	})
    63  
    64  	bufStore := memstore.New(nil)
    65  	buf := storetest.SubKV(t, ctx, bufStore, "test")
    66  	st := wbstore.New(ctx, base, bufStore)
    67  	kv, err := st.KV(ctx, "test")
    68  	if err != nil {
    69  		t.Fatalf("Create test KV: %v", err)
    70  	}
    71  	cas, err := st.CAS(ctx, "test")
    72  	if err != nil {
    73  		t.Fatalf("Create test CAS: %v", err)
    74  	}
    75  
    76  	mustWrite := func(val string) string {
    77  		t.Helper()
    78  		key, err := cas.CASPut(ctx, []byte(val))
    79  		if err != nil {
    80  			t.Fatalf("CASPut %q failed: %v", val, err)
    81  		}
    82  		return key
    83  	}
    84  	mustPut := func(key, val string, replace bool) {
    85  		t.Helper()
    86  		if err := kv.Put(ctx, blob.PutOptions{
    87  			Key:     key,
    88  			Data:    []byte(val),
    89  			Replace: replace,
    90  		}); err != nil {
    91  			t.Fatalf("Put %q failed: %v", val, err)
    92  		}
    93  	}
    94  	checkVal := func(m blob.KVCore, key, want string) {
    95  		t.Helper()
    96  		bits, err := m.Get(ctx, key)
    97  		if blob.IsKeyNotFound(err) && want == "" {
    98  			return
    99  		} else if err != nil {
   100  			t.Errorf("Get %x: unexpected error: %v", key, err)
   101  		} else if got := string(bits); got != want {
   102  			t.Errorf("Get %x: got %q, want %q", key, got, want)
   103  		}
   104  	}
   105  	checkLen := func(m blob.KVCore, want int) {
   106  		t.Helper()
   107  		got, err := m.Len(ctx)
   108  		if err != nil {
   109  			t.Errorf("Len: unexpected error: %v", err)
   110  		} else if got != int64(want) {
   111  			t.Errorf("Len: got %d, want %d", got, want)
   112  		}
   113  	}
   114  	checkList := func(m blob.KVCore, want ...string) {
   115  		t.Helper()
   116  		sort.Strings(want)
   117  		var got []string
   118  		for key, err := range m.List(ctx, "") {
   119  			if err != nil {
   120  				t.Errorf("List: unexpected error: %v", err)
   121  				break
   122  			}
   123  			got = append(got, key)
   124  		}
   125  		if diff := cmp.Diff(got, want); diff != "" {
   126  			t.Errorf("List (-got, +want):\n%s", diff)
   127  		}
   128  	}
   129  	push := func() <-chan struct{} {
   130  		p := make(chan struct{})
   131  		next <- p
   132  		return p
   133  	}
   134  
   135  	checkLen(buf, 0)
   136  	checkLen(phys, 0)
   137  
   138  	// The base writer stalls until push is called, so we can simulate a slow
   139  	// write and check the contents of the buffer.
   140  	//
   141  	// The test cases write a value, verify it lands in the cache, then unblock
   142  	// the writer and verify it lands in the base store.
   143  	k1 := mustWrite("foo")
   144  	checkVal(buf, k1, "foo") // the write should have hit the buffer
   145  	checkVal(phys, k1, "")   // it should not have hit the base
   146  	<-push()
   147  	checkVal(phys, k1, "foo")
   148  
   149  	k2 := mustWrite("bar")
   150  	checkVal(buf, k2, "bar")
   151  	checkVal(phys, k2, "")
   152  	<-push()
   153  	checkVal(phys, k2, "bar")
   154  
   155  	// A replacement Put should go directly to base, and not hit the buffer.
   156  	p := push()
   157  	mustPut("baz", "quux", true)
   158  	checkVal(buf, "baz", "")
   159  	<-p
   160  	checkVal(phys, "baz", "quux")
   161  
   162  	// A non-replacement Put should hit the buffer, and not go to base.
   163  	mustPut("frob", "argh", false)
   164  	checkVal(buf, "frob", "argh")
   165  	checkVal(phys, "frob", "")
   166  
   167  	// Verify that the buffer size reflects what we've stored.
   168  	if n, err := st.BufferLen(ctx); err != nil {
   169  		t.Errorf("BufferLen: unexpected error: %v", err)
   170  	} else if n != 1 {
   171  		t.Errorf("BufferLen = %d, want 1", n)
   172  	}
   173  
   174  	// The top-level store should see all the keys, even though they are not all
   175  	// settled yet.
   176  	checkList(buf, "frob")
   177  	checkList(phys, k1, k2, "baz")
   178  	checkList(kv, k1, k2, "baz", "frob")
   179  	checkLen(kv, 4)
   180  	<-push()
   181  
   182  	// After settlement, everything should be in the base.
   183  	checkList(phys, k1, k2, "baz", "frob")
   184  	checkList(cas, k1, k2, "baz", "frob")
   185  	checkLen(cas, 4)
   186  
   187  	checkVal(phys, k1, "foo")
   188  	checkVal(cas, k1, "foo")
   189  	checkVal(phys, k2, "bar")
   190  	checkVal(cas, k2, "bar")
   191  	checkVal(phys, "baz", "quux")
   192  	checkVal(cas, "baz", "quux")
   193  	checkVal(phys, "frob", "argh")
   194  	checkVal(cas, "frob", "argh")
   195  
   196  	if n, err := st.BufferLen(ctx); err != nil {
   197  		t.Errorf("BufferLen: unexpected error: %v", err)
   198  	} else if n != 0 {
   199  		t.Errorf("BufferLen = %d, want 0", n)
   200  	}
   201  
   202  	if err := st.Close(ctx); err != nil {
   203  		t.Errorf("Close: unexpected error: %v", err)
   204  	}
   205  }