github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/iavl/export_test.go (about)

     1  package iavl
     2  
     3  import (
     4  	"math"
     5  	"math/rand"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  // setupExportTreeBasic sets up a basic tree with a handful of
    13  // create/update/delete operations over a few versions.
    14  func setupExportTreeBasic(t require.TestingT) *ImmutableTree {
    15  	tree, err := getTestTree(0)
    16  	require.NoError(t, err)
    17  
    18  	tree.Set([]byte("x"), []byte{255})
    19  	tree.Set([]byte("z"), []byte{255})
    20  	tree.Set([]byte("a"), []byte{1})
    21  	tree.Set([]byte("b"), []byte{2})
    22  	tree.Set([]byte("c"), []byte{3})
    23  	_, _, _, err = tree.SaveVersion(false)
    24  	require.NoError(t, err)
    25  
    26  	tree.Remove([]byte("x"))
    27  	tree.Remove([]byte("b"))
    28  	tree.Set([]byte("c"), []byte{255})
    29  	tree.Set([]byte("d"), []byte{4})
    30  	_, _, _, err = tree.SaveVersion(false)
    31  	require.NoError(t, err)
    32  
    33  	tree.Set([]byte("b"), []byte{2})
    34  	tree.Set([]byte("c"), []byte{3})
    35  	tree.Set([]byte("e"), []byte{5})
    36  	tree.Remove([]byte("z"))
    37  	_, version, _, err := tree.SaveVersion(false)
    38  	require.NoError(t, err)
    39  
    40  	itree, err := tree.GetImmutable(version)
    41  	require.NoError(t, err)
    42  	return itree
    43  }
    44  
    45  // setupExportTreeRandom sets up a randomly generated tree.
    46  func setupExportTreeRandom(t *testing.T) *ImmutableTree {
    47  	const (
    48  		randSeed  = 49872768940 // For deterministic tests
    49  		keySize   = 16
    50  		valueSize = 16
    51  
    52  		versions    = 8    // number of versions to generate
    53  		versionOps  = 1024 // number of operations (create/update/delete) per version
    54  		updateRatio = 0.4  // ratio of updates out of all operations
    55  		deleteRatio = 0.2  // ratio of deletes out of all operations
    56  	)
    57  
    58  	r := rand.New(rand.NewSource(randSeed))
    59  	tree, err := getTestTree(0)
    60  	require.NoError(t, err)
    61  
    62  	var version int64
    63  	keys := make([][]byte, 0, versionOps)
    64  	for i := 0; i < versions; i++ {
    65  		for j := 0; j < versionOps; j++ {
    66  			key := make([]byte, keySize)
    67  			value := make([]byte, valueSize)
    68  
    69  			// The performance of this is likely to be terrible, but that's fine for small tests
    70  			switch {
    71  			case len(keys) > 0 && r.Float64() <= deleteRatio:
    72  				index := r.Intn(len(keys))
    73  				key = keys[index]
    74  				keys = append(keys[:index], keys[index+1:]...)
    75  				_, removed := tree.Remove(key)
    76  				require.True(t, removed)
    77  
    78  			case len(keys) > 0 && r.Float64() <= updateRatio:
    79  				key = keys[r.Intn(len(keys))]
    80  				r.Read(value)
    81  				updated := tree.Set(key, value)
    82  				require.True(t, updated)
    83  
    84  			default:
    85  				r.Read(key)
    86  				r.Read(value)
    87  				// If we get an update, set again
    88  				for tree.Set(key, value) {
    89  					key = make([]byte, keySize)
    90  					r.Read(key)
    91  				}
    92  				keys = append(keys, key)
    93  			}
    94  		}
    95  		_, version, _, err = tree.SaveVersion(false)
    96  		require.NoError(t, err)
    97  	}
    98  
    99  	require.EqualValues(t, versions, tree.Version())
   100  	require.GreaterOrEqual(t, tree.Size(), int64(math.Trunc(versions*versionOps*(1-updateRatio-deleteRatio))/2))
   101  
   102  	itree, err := tree.GetImmutable(version)
   103  	require.NoError(t, err)
   104  	return itree
   105  }
   106  
   107  // setupExportTreeSized sets up a single-version tree with a given number
   108  // of randomly generated key/value pairs, useful for benchmarking.
   109  func setupExportTreeSized(t require.TestingT, treeSize int) *ImmutableTree {
   110  	const (
   111  		randSeed  = 49872768940 // For deterministic tests
   112  		keySize   = 16
   113  		valueSize = 16
   114  	)
   115  
   116  	r := rand.New(rand.NewSource(randSeed))
   117  	tree, err := getTestTree(0)
   118  	require.NoError(t, err)
   119  
   120  	for i := 0; i < treeSize; i++ {
   121  		key := make([]byte, keySize)
   122  		value := make([]byte, valueSize)
   123  		r.Read(key)
   124  		r.Read(value)
   125  		updated := tree.Set(key, value)
   126  		if updated {
   127  			i--
   128  		}
   129  	}
   130  
   131  	_, version, _, err := tree.SaveVersion(false)
   132  	require.NoError(t, err)
   133  
   134  	itree, err := tree.GetImmutable(version)
   135  	require.NoError(t, err)
   136  
   137  	return itree
   138  }
   139  
   140  func TestExporter(t *testing.T) {
   141  	tree := setupExportTreeBasic(t)
   142  
   143  	expect := []*ExportNode{
   144  		{Key: []byte("a"), Value: []byte{1}, Version: 1, Height: 0},
   145  		{Key: []byte("b"), Value: []byte{2}, Version: 3, Height: 0},
   146  		{Key: []byte("b"), Value: nil, Version: 3, Height: 1},
   147  		{Key: []byte("c"), Value: []byte{3}, Version: 3, Height: 0},
   148  		{Key: []byte("c"), Value: nil, Version: 3, Height: 2},
   149  		{Key: []byte("d"), Value: []byte{4}, Version: 2, Height: 0},
   150  		{Key: []byte("e"), Value: []byte{5}, Version: 3, Height: 0},
   151  		{Key: []byte("e"), Value: nil, Version: 3, Height: 1},
   152  		{Key: []byte("d"), Value: nil, Version: 3, Height: 3},
   153  	}
   154  
   155  	actual := make([]*ExportNode, 0, len(expect))
   156  	exporter := tree.Export()
   157  	defer exporter.Close()
   158  	for {
   159  		node, err := exporter.Next()
   160  		if err == ExportDone {
   161  			break
   162  		}
   163  		require.NoError(t, err)
   164  		actual = append(actual, node)
   165  	}
   166  
   167  	assert.Equal(t, expect, actual)
   168  }
   169  
   170  func TestExporter_Import(t *testing.T) {
   171  	tree, err := getTestTree(0)
   172  	assert.NoError(t, err)
   173  	testcases := map[string]*ImmutableTree{
   174  		"empty tree": tree.ImmutableTree,
   175  		"basic tree": setupExportTreeBasic(t),
   176  	}
   177  	if !testing.Short() {
   178  		testcases["sized tree"] = setupExportTreeSized(t, 4096)
   179  		testcases["random tree"] = setupExportTreeRandom(t)
   180  	}
   181  
   182  	for desc, tree := range testcases {
   183  		tree := tree
   184  		t.Run(desc, func(t *testing.T) {
   185  			t.Parallel()
   186  
   187  			exporter := tree.Export()
   188  			defer exporter.Close()
   189  
   190  			newTree, err := getTestTree(0)
   191  			require.NoError(t, err)
   192  			importer, err := newTree.Import(tree.Version())
   193  			require.NoError(t, err)
   194  			defer importer.Close()
   195  
   196  			for {
   197  				item, err := exporter.Next()
   198  				if err == ExportDone {
   199  					err = importer.Commit()
   200  					require.NoError(t, err)
   201  					break
   202  				}
   203  				require.NoError(t, err)
   204  				err = importer.Add(item)
   205  				require.NoError(t, err)
   206  			}
   207  
   208  			require.Equal(t, tree.Hash(), newTree.Hash(), "Tree hash mismatch")
   209  			require.Equal(t, tree.Size(), newTree.Size(), "Tree size mismatch")
   210  			require.Equal(t, tree.Version(), newTree.Version(), "Tree version mismatch")
   211  
   212  			tree.Iterate(func(key, value []byte) bool {
   213  				index, _ := tree.GetWithIndex(key)
   214  				newIndex, newValue := newTree.GetWithIndex(key)
   215  				require.Equal(t, index, newIndex, "Index mismatch for key %v", key)
   216  				require.Equal(t, value, newValue, "Value mismatch for key %v", key)
   217  				return false
   218  			})
   219  		})
   220  	}
   221  }
   222  
   223  func TestExporter_Close(t *testing.T) {
   224  	tree := setupExportTreeSized(t, 4096)
   225  	exporter := tree.Export()
   226  
   227  	node, err := exporter.Next()
   228  	require.NoError(t, err)
   229  	require.NotNil(t, node)
   230  
   231  	exporter.Close()
   232  	node, err = exporter.Next()
   233  	require.Error(t, err)
   234  	require.Equal(t, ExportDone, err)
   235  	require.Nil(t, node)
   236  
   237  	node, err = exporter.Next()
   238  	require.Error(t, err)
   239  	require.Equal(t, ExportDone, err)
   240  	require.Nil(t, node)
   241  
   242  	exporter.Close()
   243  	exporter.Close()
   244  }
   245  
   246  func TestExporter_DeleteVersionErrors(t *testing.T) {
   247  	tree, err := getTestTree(0)
   248  
   249  	tree.Set([]byte("a"), []byte{1})
   250  	_, _, _, err = tree.SaveVersion(false)
   251  	require.NoError(t, err)
   252  
   253  	tree.Set([]byte("b"), []byte{2})
   254  	_, _, _, err = tree.SaveVersion(false)
   255  	require.NoError(t, err)
   256  
   257  	tree.Set([]byte("c"), []byte{3})
   258  	_, _, _, err = tree.SaveVersion(false)
   259  	require.NoError(t, err)
   260  
   261  	itree, err := tree.GetImmutable(2)
   262  	require.NoError(t, err)
   263  	exporter := itree.Export()
   264  	defer exporter.Close()
   265  
   266  	err = tree.DeleteVersion(2)
   267  	require.Error(t, err)
   268  	err = tree.DeleteVersion(1)
   269  	require.NoError(t, err)
   270  
   271  	exporter.Close()
   272  	err = tree.DeleteVersion(2)
   273  	require.NoError(t, err)
   274  }
   275  
   276  func BenchmarkExport(b *testing.B) {
   277  	b.StopTimer()
   278  	tree := setupExportTreeSized(b, 4096)
   279  	b.StartTimer()
   280  	for n := 0; n < b.N; n++ {
   281  		exporter := tree.Export()
   282  		for {
   283  			_, err := exporter.Next()
   284  			if err == ExportDone {
   285  				break
   286  			} else if err != nil {
   287  				b.Error(err)
   288  			}
   289  		}
   290  		exporter.Close()
   291  	}
   292  }