github.com/MetalBlockchain/metalgo@v1.11.9/database/corruptabledb/db_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package corruptabledb 5 6 import ( 7 "context" 8 "errors" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "go.uber.org/mock/gomock" 13 14 "github.com/MetalBlockchain/metalgo/database" 15 "github.com/MetalBlockchain/metalgo/database/memdb" 16 ) 17 18 var errTest = errors.New("non-nil error") 19 20 func newDB() *Database { 21 baseDB := memdb.New() 22 return New(baseDB) 23 } 24 25 func TestInterface(t *testing.T) { 26 for name, test := range database.Tests { 27 t.Run(name, func(t *testing.T) { 28 test(t, newDB()) 29 }) 30 } 31 } 32 33 func FuzzKeyValue(f *testing.F) { 34 database.FuzzKeyValue(f, newDB()) 35 } 36 37 func FuzzNewIteratorWithPrefix(f *testing.F) { 38 database.FuzzNewIteratorWithPrefix(f, newDB()) 39 } 40 41 func FuzzNewIteratorWithStartAndPrefix(f *testing.F) { 42 database.FuzzNewIteratorWithStartAndPrefix(f, newDB()) 43 } 44 45 // TestCorruption tests to make sure corruptabledb wrapper works as expected. 46 func TestCorruption(t *testing.T) { 47 key := []byte("hello") 48 value := []byte("world") 49 tests := map[string]func(db database.Database) error{ 50 "corrupted has": func(db database.Database) error { 51 _, err := db.Has(key) 52 return err 53 }, 54 "corrupted get": func(db database.Database) error { 55 _, err := db.Get(key) 56 return err 57 }, 58 "corrupted put": func(db database.Database) error { 59 return db.Put(key, value) 60 }, 61 "corrupted delete": func(db database.Database) error { 62 return db.Delete(key) 63 }, 64 "corrupted batch": func(db database.Database) error { 65 corruptableBatch := db.NewBatch() 66 require.NotNil(t, corruptableBatch) 67 68 require.NoError(t, corruptableBatch.Put(key, value)) 69 70 return corruptableBatch.Write() 71 }, 72 "corrupted healthcheck": func(db database.Database) error { 73 _, err := db.HealthCheck(context.Background()) 74 return err 75 }, 76 } 77 corruptableDB := newDB() 78 _ = corruptableDB.handleError(errTest) 79 for name, testFn := range tests { 80 t.Run(name, func(tt *testing.T) { 81 err := testFn(corruptableDB) 82 require.ErrorIs(tt, err, errTest) 83 }) 84 } 85 } 86 87 func TestIterator(t *testing.T) { 88 errIter := errors.New("iterator error") 89 90 type test struct { 91 name string 92 databaseErrBefore error 93 modifyIter func(*gomock.Controller, *iterator) 94 op func(*require.Assertions, *iterator) 95 expectedErr error 96 } 97 98 tests := []test{ 99 { 100 name: "corrupted database; Next", 101 databaseErrBefore: errTest, 102 expectedErr: errTest, 103 modifyIter: func(*gomock.Controller, *iterator) {}, 104 op: func(require *require.Assertions, iter *iterator) { 105 require.False(iter.Next()) 106 }, 107 }, 108 { 109 name: "Next corrupts database", 110 databaseErrBefore: nil, 111 expectedErr: errIter, 112 modifyIter: func(ctrl *gomock.Controller, iter *iterator) { 113 mockInnerIter := database.NewMockIterator(ctrl) 114 mockInnerIter.EXPECT().Next().Return(false) 115 mockInnerIter.EXPECT().Error().Return(errIter) 116 iter.Iterator = mockInnerIter 117 }, 118 op: func(require *require.Assertions, iter *iterator) { 119 require.False(iter.Next()) 120 }, 121 }, 122 { 123 name: "corrupted database; Error", 124 databaseErrBefore: errTest, 125 expectedErr: errTest, 126 modifyIter: func(*gomock.Controller, *iterator) {}, 127 op: func(require *require.Assertions, iter *iterator) { 128 err := iter.Error() 129 require.ErrorIs(err, errTest) 130 }, 131 }, 132 { 133 name: "Error corrupts database", 134 databaseErrBefore: nil, 135 expectedErr: errIter, 136 modifyIter: func(ctrl *gomock.Controller, iter *iterator) { 137 mockInnerIter := database.NewMockIterator(ctrl) 138 mockInnerIter.EXPECT().Error().Return(errIter) 139 iter.Iterator = mockInnerIter 140 }, 141 op: func(require *require.Assertions, iter *iterator) { 142 err := iter.Error() 143 require.ErrorIs(err, errIter) 144 }, 145 }, 146 { 147 name: "corrupted database; Key", 148 databaseErrBefore: errTest, 149 expectedErr: errTest, 150 modifyIter: func(*gomock.Controller, *iterator) {}, 151 op: func(_ *require.Assertions, iter *iterator) { 152 _ = iter.Key() 153 }, 154 }, 155 { 156 name: "corrupted database; Value", 157 databaseErrBefore: errTest, 158 expectedErr: errTest, 159 modifyIter: func(*gomock.Controller, *iterator) {}, 160 op: func(_ *require.Assertions, iter *iterator) { 161 _ = iter.Value() 162 }, 163 }, 164 { 165 name: "corrupted database; Release", 166 databaseErrBefore: errTest, 167 expectedErr: errTest, 168 modifyIter: func(*gomock.Controller, *iterator) {}, 169 op: func(_ *require.Assertions, iter *iterator) { 170 iter.Release() 171 }, 172 }, 173 } 174 175 for _, tt := range tests { 176 t.Run(tt.name, func(t *testing.T) { 177 require := require.New(t) 178 ctrl := gomock.NewController(t) 179 180 // Make a database 181 corruptableDB := newDB() 182 // Put a key-value pair in the database. 183 require.NoError(corruptableDB.Put([]byte{0}, []byte{1})) 184 185 // Mark database as corupted, if applicable 186 _ = corruptableDB.handleError(tt.databaseErrBefore) 187 188 // Make an iterator 189 iter := &iterator{ 190 Iterator: corruptableDB.NewIterator(), 191 db: corruptableDB, 192 } 193 194 // Modify the iterator (optional) 195 tt.modifyIter(ctrl, iter) 196 197 // Do an iterator operation 198 tt.op(require, iter) 199 200 err := corruptableDB.corrupted() 201 require.ErrorIs(err, tt.expectedErr) 202 }) 203 } 204 }