github.com/creachadair/ffs@v0.17.3/blob/store_test.go (about) 1 // Copyright 2021 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 blob_test 16 17 import ( 18 "errors" 19 "fmt" 20 "path" 21 "reflect" 22 "runtime" 23 "testing" 24 25 "github.com/creachadair/ffs/blob" 26 "github.com/creachadair/ffs/blob/memstore" 27 "github.com/creachadair/mds/mapset" 28 gocmp "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 ) 31 32 func TestSentinelErrors(t *testing.T) { 33 plain := errors.New("it's not for you") 34 keyExists := fmt.Errorf("test: %w", blob.ErrKeyExists) 35 keyNotFound := fmt.Errorf("test: %w", blob.ErrKeyNotFound) 36 37 t.Run("ErrorsIs", func(t *testing.T) { 38 tests := []struct { 39 input error 40 is error 41 want bool 42 }{ 43 {nil, blob.ErrKeyExists, false}, 44 {nil, blob.ErrKeyNotFound, false}, 45 {plain, blob.ErrKeyExists, false}, 46 {plain, blob.ErrKeyNotFound, false}, 47 {keyExists, blob.ErrKeyExists, true}, 48 {keyExists, blob.ErrKeyNotFound, false}, 49 {keyNotFound, blob.ErrKeyExists, false}, 50 {keyNotFound, blob.ErrKeyNotFound, true}, 51 {blob.KeyExists("x"), blob.ErrKeyExists, true}, 52 {blob.KeyExists("x"), blob.ErrKeyNotFound, false}, 53 {blob.KeyNotFound("y"), blob.ErrKeyExists, false}, 54 {blob.KeyNotFound("y"), blob.ErrKeyNotFound, true}, 55 } 56 for _, test := range tests { 57 got := errors.Is(test.input, test.is) 58 if got != test.want { 59 t.Errorf("Error %q is %q: got %v, want %v", test.input, test.is, got, test.want) 60 } 61 } 62 }) 63 64 t.Run("ErrorChecks", func(t *testing.T) { 65 tests := []struct { 66 input error 67 check func(error) bool 68 want bool 69 }{ 70 {nil, blob.IsKeyExists, false}, 71 {nil, blob.IsKeyNotFound, false}, 72 {plain, blob.IsKeyExists, false}, 73 {plain, blob.IsKeyNotFound, false}, 74 {keyExists, blob.IsKeyExists, true}, 75 {keyExists, blob.IsKeyNotFound, false}, 76 {keyNotFound, blob.IsKeyExists, false}, 77 {keyNotFound, blob.IsKeyNotFound, true}, 78 } 79 for i, test := range tests { 80 81 got := test.check(test.input) 82 if got != test.want { 83 t.Errorf("[%d] Error %q check %q: got %v, want %v", 84 i+1, test.input, funcBaseName(test.check), got, test.want) 85 } 86 } 87 }) 88 } 89 90 func funcBaseName(v any) string { 91 _, name := path.Split(runtime.FuncForPC(reflect.ValueOf(v).Pointer()).Name()) 92 return name 93 } 94 95 func TestKeyError(t *testing.T) { 96 const needle = "magic test key" 97 98 tests := []struct { 99 input error 100 base error 101 }{ 102 {blob.KeyExists(needle), blob.ErrKeyExists}, 103 {blob.KeyNotFound(needle), blob.ErrKeyNotFound}, 104 } 105 for _, test := range tests { 106 v, ok := test.input.(*blob.KeyError) 107 if !ok { 108 t.Errorf("Error %q is not a KeyError", test.input) 109 continue 110 } 111 if v.Key != needle { 112 t.Errorf("Error %q: got key %q, want %q", test.input, v.Key, needle) 113 } 114 if v.Err != test.base { 115 t.Errorf("Error %q: got base %v, want %v", test.input, v.Err, test.base) 116 } 117 } 118 } 119 120 func TestSyncKeys(t *testing.T) { 121 kv := memstore.NewKV().Init(map[string]string{ 122 "1": "one", 123 "2": "two", 124 "3": "three", 125 "4": "four", 126 "5": "five", 127 }) 128 cas := blob.CASFromKV(kv) 129 130 ctx := t.Context() 131 check := func(ks blob.KVCore, keys []string, want ...string) func(t *testing.T) { 132 return func(t *testing.T) { 133 t.Helper() 134 got, err := blob.SyncKeys(ctx, kv, keys) 135 if err != nil { 136 t.Fatalf("SyncKeys: unexpected error: %v", err) 137 } 138 if diff := gocmp.Diff(got, mapset.New(want...), cmpopts.EquateEmpty()); diff != "" { 139 t.Fatalf("SyncKeys (-got, +want):\n%s", diff) 140 } 141 } 142 } 143 144 t.Run("Empty", func(t *testing.T) { 145 t.Run("KV", check(kv, nil)) 146 t.Run("CAS", check(cas, nil)) 147 }) 148 t.Run("NoneMissing", func(t *testing.T) { 149 t.Run("KV", check(kv, []string{"1", "3", "4"})) 150 t.Run("CAS", check(cas, []string{"1", "3", "4"})) 151 }) 152 t.Run("SomeMissing", func(t *testing.T) { 153 t.Run("KV", check(kv, []string{"1", "6", "4", "7"}, "6", "7")) 154 t.Run("CAS", check(cas, []string{"1", "6", "4", "7"}, "6", "7")) 155 }) 156 t.Run("AllMissing", func(t *testing.T) { 157 t.Run("KV", check(kv, []string{"10", "50", "90", "0", "8"}, "0", "10", "50", "8", "90")) 158 t.Run("CAS", check(cas, []string{"10", "50", "90", "0", "8"}, "0", "10", "50", "8", "90")) 159 }) 160 }