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  }