github.com/decred/dcrlnd@v0.7.6/kvdb/etcd/stm_test.go (about) 1 //go:build kvdb_etcd 2 // +build kvdb_etcd 3 4 package etcd 5 6 import ( 7 "context" 8 "errors" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 ) 13 14 func reverseKVs(a []KV) []KV { 15 for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 16 a[i], a[j] = a[j], a[i] 17 } 18 19 return a 20 } 21 22 func TestPutToEmpty(t *testing.T) { 23 t.Parallel() 24 25 f := NewEtcdTestFixture(t) 26 ctx, cancel := context.WithCancel(context.Background()) 27 28 txQueue := NewCommitQueue(ctx) 29 defer func() { 30 cancel() 31 f.Cleanup() 32 txQueue.Stop() 33 }() 34 35 db, err := newEtcdBackend(ctx, f.BackendConfig()) 36 require.NoError(t, err) 37 38 apply := func(stm STM) error { 39 stm.Put("123", "abc") 40 return nil 41 } 42 43 callCount, err := RunSTM(db.cli, apply, txQueue) 44 require.NoError(t, err) 45 require.Equal(t, 1, callCount) 46 47 require.Equal(t, "abc", f.Get("123")) 48 } 49 50 func TestGetPutDel(t *testing.T) { 51 t.Parallel() 52 53 f := NewEtcdTestFixture(t) 54 ctx, cancel := context.WithCancel(context.Background()) 55 56 txQueue := NewCommitQueue(ctx) 57 defer func() { 58 cancel() 59 f.Cleanup() 60 txQueue.Stop() 61 }() 62 63 testKeyValues := []KV{ 64 {"a", "1"}, 65 {"b", "2"}, 66 {"c", "3"}, 67 {"d", "4"}, 68 {"e", "5"}, 69 } 70 71 // Extra 2 => Get(x), Commit() 72 expectedCallCount := len(testKeyValues) + 2 73 74 for _, kv := range testKeyValues { 75 f.Put(kv.key, kv.val) 76 } 77 78 db, err := newEtcdBackend(ctx, f.BackendConfig()) 79 require.NoError(t, err) 80 81 apply := func(stm STM) error { 82 // Get some non existing keys. 83 v, err := stm.Get("") 84 require.NoError(t, err) 85 require.Nil(t, v) 86 87 // Fetches: 1. 88 v, err = stm.Get("x") 89 require.NoError(t, err) 90 require.Nil(t, v) 91 92 // Get all existing keys. Fetches: len(testKeyValues) 93 for _, kv := range testKeyValues { 94 v, err = stm.Get(kv.key) 95 require.NoError(t, err) 96 require.Equal(t, []byte(kv.val), v) 97 } 98 99 // Overwrite, then delete an existing key. 100 stm.Put("c", "6") 101 102 v, err = stm.Get("c") 103 require.NoError(t, err) 104 require.Equal(t, []byte("6"), v) 105 106 stm.Del("c") 107 108 v, err = stm.Get("c") 109 require.NoError(t, err) 110 require.Nil(t, v) 111 112 // Re-add the deleted key. 113 stm.Put("c", "7") 114 115 v, err = stm.Get("c") 116 require.NoError(t, err) 117 require.Equal(t, []byte("7"), v) 118 119 // Add a new key. 120 stm.Put("x", "x") 121 122 v, err = stm.Get("x") 123 require.NoError(t, err) 124 require.Equal(t, []byte("x"), v) 125 126 return nil 127 } 128 129 callCount, err := RunSTM(db.cli, apply, txQueue) 130 require.NoError(t, err) 131 require.Equal(t, expectedCallCount, callCount) 132 133 require.Equal(t, "1", f.Get("a")) 134 require.Equal(t, "2", f.Get("b")) 135 require.Equal(t, "7", f.Get("c")) 136 require.Equal(t, "4", f.Get("d")) 137 require.Equal(t, "5", f.Get("e")) 138 require.Equal(t, "x", f.Get("x")) 139 } 140 141 func TestFirstLastNextPrev(t *testing.T) { 142 t.Parallel() 143 144 testFirstLastNextPrev(t, nil, nil, 41) 145 testFirstLastNextPrev(t, nil, []string{"k"}, 4) 146 testFirstLastNextPrev(t, nil, []string{"k", "w"}, 2) 147 testFirstLastNextPrev(t, []string{"kb"}, nil, 42) 148 testFirstLastNextPrev(t, []string{"kb", "ke"}, nil, 42) 149 testFirstLastNextPrev(t, []string{"kb", "ke", "w"}, []string{"k", "w"}, 2) 150 } 151 152 func testFirstLastNextPrev(t *testing.T, prefetchKeys []string, 153 prefetchRange []string, expectedCallCount int) { 154 155 f := NewEtcdTestFixture(t) 156 ctx, cancel := context.WithCancel(context.Background()) 157 158 txQueue := NewCommitQueue(ctx) 159 defer func() { 160 cancel() 161 f.Cleanup() 162 txQueue.Stop() 163 }() 164 165 testKeyValues := []KV{ 166 {"kb", "1"}, 167 {"kc", "2"}, 168 {"kda", "3"}, 169 {"ke", "4"}, 170 {"w", "w"}, 171 } 172 for _, kv := range testKeyValues { 173 f.Put(kv.key, kv.val) 174 } 175 176 db, err := newEtcdBackend(ctx, f.BackendConfig()) 177 require.NoError(t, err) 178 179 apply := func(stm STM) error { 180 stm.Prefetch(prefetchKeys, prefetchRange) 181 182 // First/Last on valid multi item interval. 183 kv, err := stm.First("k") 184 require.NoError(t, err) 185 require.Equal(t, &KV{"kb", "1"}, kv) 186 187 kv, err = stm.Last("k") 188 require.NoError(t, err) 189 require.Equal(t, &KV{"ke", "4"}, kv) 190 191 // First/Last on single item interval. 192 kv, err = stm.First("w") 193 require.NoError(t, err) 194 require.Equal(t, &KV{"w", "w"}, kv) 195 196 kv, err = stm.Last("w") 197 require.NoError(t, err) 198 require.Equal(t, &KV{"w", "w"}, kv) 199 200 // Non existing. 201 val, err := stm.Get("ke1") 202 require.Nil(t, val) 203 require.Nil(t, err) 204 205 val, err = stm.Get("ke2") 206 require.Nil(t, val) 207 require.Nil(t, err) 208 209 // Next/Prev on start/end. 210 kv, err = stm.Next("k", "ke") 211 require.NoError(t, err) 212 require.Nil(t, kv) 213 214 // Non existing. 215 val, err = stm.Get("ka") 216 require.Nil(t, val) 217 require.Nil(t, err) 218 219 kv, err = stm.Prev("k", "kb") 220 require.NoError(t, err) 221 require.Nil(t, kv) 222 223 // Next/Prev in the middle. 224 kv, err = stm.Next("k", "kc") 225 require.NoError(t, err) 226 require.Equal(t, &KV{"kda", "3"}, kv) 227 228 kv, err = stm.Prev("k", "ke") 229 require.NoError(t, err) 230 require.Equal(t, &KV{"kda", "3"}, kv) 231 232 // Delete first item, then add an item before the 233 // deleted one. Check that First/Next will "jump" 234 // over the deleted item and return the new first. 235 stm.Del("kb") 236 stm.Put("ka", "0") 237 238 kv, err = stm.First("k") 239 require.NoError(t, err) 240 require.Equal(t, &KV{"ka", "0"}, kv) 241 242 kv, err = stm.Prev("k", "kc") 243 require.NoError(t, err) 244 require.Equal(t, &KV{"ka", "0"}, kv) 245 246 // Similarly test that a new end is returned if 247 // the old end is deleted first. 248 stm.Del("ke") 249 stm.Put("kf", "5") 250 251 kv, err = stm.Last("k") 252 require.NoError(t, err) 253 require.Equal(t, &KV{"kf", "5"}, kv) 254 255 kv, err = stm.Next("k", "kda") 256 require.NoError(t, err) 257 require.Equal(t, &KV{"kf", "5"}, kv) 258 259 // Overwrite one in the middle. 260 stm.Put("kda", "6") 261 262 kv, err = stm.Next("k", "kc") 263 require.NoError(t, err) 264 require.Equal(t, &KV{"kda", "6"}, kv) 265 266 // Add three in the middle, then delete one. 267 stm.Put("kdb", "7") 268 stm.Put("kdc", "8") 269 stm.Put("kdd", "9") 270 stm.Del("kdc") 271 272 // Check that stepping from first to last returns 273 // the expected sequence. 274 var kvs []KV 275 276 curr, err := stm.First("k") 277 require.NoError(t, err) 278 279 for curr != nil { 280 kvs = append(kvs, *curr) 281 curr, err = stm.Next("k", curr.key) 282 require.NoError(t, err) 283 } 284 285 expected := []KV{ 286 {"ka", "0"}, 287 {"kc", "2"}, 288 {"kda", "6"}, 289 {"kdb", "7"}, 290 {"kdd", "9"}, 291 {"kf", "5"}, 292 } 293 require.Equal(t, expected, kvs) 294 295 // Similarly check that stepping from last to first 296 // returns the expected sequence. 297 kvs = []KV{} 298 299 curr, err = stm.Last("k") 300 require.NoError(t, err) 301 302 for curr != nil { 303 kvs = append(kvs, *curr) 304 curr, err = stm.Prev("k", curr.key) 305 require.NoError(t, err) 306 } 307 308 expected = reverseKVs(expected) 309 require.Equal(t, expected, kvs) 310 311 return nil 312 } 313 314 callCount, err := RunSTM(db.cli, apply, txQueue) 315 require.NoError(t, err) 316 require.Equal(t, expectedCallCount, callCount) 317 318 require.Equal(t, "0", f.Get("ka")) 319 require.Equal(t, "2", f.Get("kc")) 320 require.Equal(t, "6", f.Get("kda")) 321 require.Equal(t, "7", f.Get("kdb")) 322 require.Equal(t, "9", f.Get("kdd")) 323 require.Equal(t, "5", f.Get("kf")) 324 require.Equal(t, "w", f.Get("w")) 325 } 326 327 func TestCommitError(t *testing.T) { 328 t.Parallel() 329 330 f := NewEtcdTestFixture(t) 331 ctx, cancel := context.WithCancel(context.Background()) 332 333 txQueue := NewCommitQueue(ctx) 334 defer func() { 335 cancel() 336 f.Cleanup() 337 txQueue.Stop() 338 }() 339 340 db, err := newEtcdBackend(ctx, f.BackendConfig()) 341 require.NoError(t, err) 342 343 // Preset DB state. 344 f.Put("123", "xyz") 345 346 // Count the number of applies. 347 cnt := 0 348 349 apply := func(stm STM) error { 350 // STM must have the key/value. 351 val, err := stm.Get("123") 352 require.NoError(t, err) 353 354 if cnt == 0 { 355 require.Equal(t, []byte("xyz"), val) 356 357 // Put a conflicting key/value during the first apply. 358 f.Put("123", "def") 359 } 360 361 // We'd expect to 362 stm.Put("123", "abc") 363 364 cnt++ 365 return nil 366 } 367 368 callCount, err := RunSTM(db.cli, apply, txQueue) 369 require.NoError(t, err) 370 require.Equal(t, 2, cnt) 371 // Get() + 2 * Commit(). 372 require.Equal(t, 3, callCount) 373 374 require.Equal(t, "abc", f.Get("123")) 375 } 376 377 func TestManualTxError(t *testing.T) { 378 t.Parallel() 379 380 f := NewEtcdTestFixture(t) 381 ctx, cancel := context.WithCancel(context.Background()) 382 383 txQueue := NewCommitQueue(ctx) 384 defer func() { 385 cancel() 386 f.Cleanup() 387 txQueue.Stop() 388 }() 389 390 db, err := newEtcdBackend(ctx, f.BackendConfig()) 391 require.NoError(t, err) 392 393 // Preset DB state. 394 f.Put("123", "xyz") 395 396 stm := NewSTM(db.cli, txQueue) 397 398 val, err := stm.Get("123") 399 require.NoError(t, err) 400 require.Equal(t, []byte("xyz"), val) 401 402 // Put a conflicting key/value. 403 f.Put("123", "def") 404 405 // Should still get the original version. 406 val, err = stm.Get("123") 407 require.NoError(t, err) 408 require.Equal(t, []byte("xyz"), val) 409 410 // Commit will fail with CommitError. 411 err = stm.Commit() 412 var e CommitError 413 require.True(t, errors.As(err, &e)) 414 415 // We expect that the transacton indeed did not commit. 416 require.Equal(t, "def", f.Get("123")) 417 }