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 }