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 }